From 1688ac85c9f074bf85cbfb3e013e56a5125886d0 Mon Sep 17 00:00:00 2001 From: adilallo <39313955+adilallo@users.noreply.github.com> Date: Wed, 20 May 2026 22:17:00 -0600 Subject: [PATCH] Update learn page --- app/(marketing)/blog/[slug]/page.tsx | 66 +--------- app/(marketing)/learn/page.tsx | 34 ++---- .../ContentContainer.container.tsx | 45 ++++--- .../ContentContainer.view.tsx | 5 +- .../ContentThumbnailTemplate.container.tsx | 13 +- .../ContentThumbnailTemplate.types.ts | 7 ++ .../ContentThumbnailTemplate.view.tsx | 82 +++++++++---- .../RelatedArticles/RelatedArticles.view.tsx | 5 +- ...ing-burnout-sustainability-in-the-ruins.md | 75 ++++++++++++ content/blog/building-community-trust.md | 27 ----- ...gital-mediation-and-the-death-of-nuance.md | 77 ++++++++++++ .../blog/how-chaos-concentrates-control.md | 75 ++++++++++++ ...ntegrating-new-members-without-dilution.md | 53 ++++++++ ...ge-management-and-institutional-amnesia.md | 63 ++++++++++ .../making-decisions-without-hierarchy.md | 25 ++++ .../blog/operational-security-mutual-aid.md | 35 +++--- content/blog/resolving-active-conflicts.md | 20 ++-- lib/assetUtils.ts | 17 +++ lib/content.ts | 4 +- ...sustainability-in-the-ruins-horizontal.svg | 31 +++++ ...urnout-sustainability-in-the-ruins-tag.svg | 18 +++ ...t-sustainability-in-the-ruins-vertical.svg | 31 +++++ .../building-community-trust-horizontal.svg | 28 ----- .../building-community-trust-vertical.svg | 34 ------ ...ion-and-the-death-of-nuance-horizontal.svg | 31 +++++ ...-mediation-and-the-death-of-nuance-tag.svg | 14 +++ ...ation-and-the-death-of-nuance-vertical.svg | 31 +++++ ...-chaos-concentrates-control-horizontal.svg | 31 +++++ .../how-chaos-concentrates-control-tag.svg | 16 +++ ...ow-chaos-concentrates-control-vertical.svg | 31 +++++ ...ew-members-without-dilution-horizontal.svg | 31 +++++ ...ating-new-members-without-dilution-tag.svg | 14 +++ ...-new-members-without-dilution-vertical.svg | 31 +++++ ...t-and-institutional-amnesia-horizontal.svg | 31 +++++ ...nagement-and-institutional-amnesia-tag.svg | 18 +++ ...ent-and-institutional-amnesia-vertical.svg | 31 +++++ ...decisions-without-hierarchy-horizontal.svg | 31 +++++ ...making-decisions-without-hierarchy-tag.svg | 16 +++ ...g-decisions-without-hierarchy-vertical.svg | 31 +++++ .../operational-security-mutual-aid-tag.svg | 18 +++ .../blog/resolving-active-conflicts-tag.svg | 14 +++ .../ContentThumbnailTemplate.stories.js | 14 ++- tests/pages/learn.test.tsx | 113 ++++++++++++++++++ tests/unit/ContentContainer.test.jsx | 26 ++-- tests/unit/ContentThumbnailTemplate.test.jsx | 110 +++-------------- 45 files changed, 1203 insertions(+), 350 deletions(-) create mode 100644 content/blog/avoiding-burnout-sustainability-in-the-ruins.md delete mode 100644 content/blog/building-community-trust.md create mode 100644 content/blog/digital-mediation-and-the-death-of-nuance.md create mode 100644 content/blog/how-chaos-concentrates-control.md create mode 100644 content/blog/integrating-new-members-without-dilution.md create mode 100644 content/blog/knowledge-management-and-institutional-amnesia.md create mode 100644 content/blog/making-decisions-without-hierarchy.md create mode 100644 public/content/blog/avoiding-burnout-sustainability-in-the-ruins-horizontal.svg create mode 100644 public/content/blog/avoiding-burnout-sustainability-in-the-ruins-tag.svg create mode 100644 public/content/blog/avoiding-burnout-sustainability-in-the-ruins-vertical.svg delete mode 100644 public/content/blog/building-community-trust-horizontal.svg delete mode 100644 public/content/blog/building-community-trust-vertical.svg create mode 100644 public/content/blog/digital-mediation-and-the-death-of-nuance-horizontal.svg create mode 100644 public/content/blog/digital-mediation-and-the-death-of-nuance-tag.svg create mode 100644 public/content/blog/digital-mediation-and-the-death-of-nuance-vertical.svg create mode 100644 public/content/blog/how-chaos-concentrates-control-horizontal.svg create mode 100644 public/content/blog/how-chaos-concentrates-control-tag.svg create mode 100644 public/content/blog/how-chaos-concentrates-control-vertical.svg create mode 100644 public/content/blog/integrating-new-members-without-dilution-horizontal.svg create mode 100644 public/content/blog/integrating-new-members-without-dilution-tag.svg create mode 100644 public/content/blog/integrating-new-members-without-dilution-vertical.svg create mode 100644 public/content/blog/knowledge-management-and-institutional-amnesia-horizontal.svg create mode 100644 public/content/blog/knowledge-management-and-institutional-amnesia-tag.svg create mode 100644 public/content/blog/knowledge-management-and-institutional-amnesia-vertical.svg create mode 100644 public/content/blog/making-decisions-without-hierarchy-horizontal.svg create mode 100644 public/content/blog/making-decisions-without-hierarchy-tag.svg create mode 100644 public/content/blog/making-decisions-without-hierarchy-vertical.svg create mode 100644 public/content/blog/operational-security-mutual-aid-tag.svg create mode 100644 public/content/blog/resolving-active-conflicts-tag.svg create mode 100644 tests/pages/learn.test.tsx diff --git a/app/(marketing)/blog/[slug]/page.tsx b/app/(marketing)/blog/[slug]/page.tsx index b1339c3..c7dc218 100644 --- a/app/(marketing)/blog/[slug]/page.tsx +++ b/app/(marketing)/blog/[slug]/page.tsx @@ -4,7 +4,7 @@ import dynamic from "next/dynamic"; import { getBlogPostBySlug, getAllBlogPosts as getAllPosts, - type BlogPost, + getRelatedBlogPosts, } from "../../../../lib/content"; import { logger } from "../../../../lib/logger"; import ContentBanner from "../../../components/sections/ContentBanner"; @@ -111,66 +111,12 @@ export default async function BlogPostPage({ params }: PageProps) { // Get related articles with improved algorithm const allPosts = getAllPosts(); - - // Create slug order for consistent background cycling const slugOrder = allPosts.map((post) => post.slug); - - // Simple related articles algorithm based on content similarity - const getRelatedArticles = ( - currentPost: BlogPost, - allPosts: BlogPost[], - limit = 3, - ): BlogPost[] => { - const otherPosts = allPosts.filter((p) => p.slug !== currentPost.slug); - - // Score posts based on content similarity - const scoredPosts = otherPosts.map((post) => { - let score = 0; - - // Check for similar keywords in title and description - const currentTitle = currentPost.frontmatter.title.toLowerCase(); - const currentDesc = currentPost.frontmatter.description.toLowerCase(); - const postTitle = post.frontmatter.title.toLowerCase(); - const postDesc = post.frontmatter.description.toLowerCase(); - - // Common keywords that indicate similarity - const keywords = [ - "community", - "conflict", - "decision", - "governance", - "security", - "trust", - "collaboration", - "organization", - ]; - - keywords.forEach((keyword) => { - if (currentTitle.includes(keyword) && postTitle.includes(keyword)) - score += 3; - if (currentDesc.includes(keyword) && postDesc.includes(keyword)) - score += 2; - if (currentTitle.includes(keyword) && postDesc.includes(keyword)) - score += 1; - if (currentDesc.includes(keyword) && postTitle.includes(keyword)) - score += 1; - }); - - return { ...post, score }; - }); - - // Sort by score and return top posts - return scoredPosts - .sort((a, b) => b.score - a.score) - .slice(0, limit) - .map(({ score, ...post }) => { - // Score used for sorting, removed from final result - void score; - return post; - }); - }; - - const relatedArticles = getRelatedArticles(post, allPosts); + const relatedArticles = getRelatedBlogPosts( + post.slug, + post.frontmatter.related, + 3, + ); // Generate structured data for search engines const structuredData = { diff --git a/app/(marketing)/learn/page.tsx b/app/(marketing)/learn/page.tsx index 49909ee..3671bfe 100644 --- a/app/(marketing)/learn/page.tsx +++ b/app/(marketing)/learn/page.tsx @@ -6,10 +6,8 @@ import AskOrganizer from "../../components/sections/AskOrganizer"; import { getAllBlogPosts } from "../../../lib/content"; export default function LearnPage() { - // Get real blog posts from the content system const allPosts = getAllBlogPosts(); - // Use direct message access for server components const t = (key: string) => getTranslation(messages, key); const contentLockupData = { @@ -31,36 +29,24 @@ export default function LearnPage() {
- {/* Horizontal list (below smd) */}
- {allPosts.slice(0, 3).map((post, index) => ( + {allPosts.map((post) => ( ))}
- {/* smd and up: 2x3 grid of vertical thumbnails, repeat posts as needed */} -
- {Array.from({ length: 16 }).map((_, i) => { - const post = allPosts[i % allPosts.length]; - return ( - = 6 ? "hidden lg2:block" : ""} ${ - i >= 10 ? "xl:hidden" : "" - }`} - /> - ); - })} +
+ {allPosts.map((post) => ( + + ))}
diff --git a/app/components/content/ContentContainer/ContentContainer.container.tsx b/app/components/content/ContentContainer/ContentContainer.container.tsx index c6a4c75..a60ca4a 100644 --- a/app/components/content/ContentContainer/ContentContainer.container.tsx +++ b/app/components/content/ContentContainer/ContentContainer.container.tsx @@ -1,7 +1,16 @@ "use client"; +/** + * Figma: "Components" / Container (19614-14838, 19003-23432). + * XS thumbnail copy: title 18/22, description 12/16, metadata 10/14. + */ import { memo } from "react"; -import { getAssetPath, ASSETS } from "../../../../lib/assetUtils"; +import { + getAssetPath, + ASSETS, + contentBlogTagPath, + CONTENT_CATALOG_SLUG_ORDER, +} from "../../../../lib/assetUtils"; import ContentContainerView from "./ContentContainer.view"; import type { ContentContainerProps } from "./ContentContainer.types"; @@ -25,7 +34,7 @@ const ContentContainerContainer = memo( const bodyColor = onLight ? "text-[var(--color-content-default-secondary)]" : "text-[var(--color-content-inverse-brand-royal)]"; - // Get the corresponding icon based on the same logic as background images + const getIconImage = (slug: string): string => { const icons = [ getAssetPath(ASSETS.ICON_1), @@ -35,23 +44,24 @@ const ContentContainerContainer = memo( if (!slug) return icons[0]; - // Use the same cycling logic as background images to ensure matching - const slugOrder = [ - "building-community-trust", - "operational-security-mutual-aid", - "making-decisions-without-hierarchy", - "resolving-active-conflicts", - ]; - const index = slugOrder.indexOf(slug); - const finalIndex = index >= 0 ? index % icons.length : 0; - return icons[finalIndex]; + const index = CONTENT_CATALOG_SLUG_ORDER.indexOf( + slug as (typeof CONTENT_CATALOG_SLUG_ORDER)[number], + ); + if (index >= 0) { + return contentBlogTagPath(slug); + } + + const fallbackIndex = + Math.abs( + slug.split("").reduce((acc, c) => acc + c.charCodeAt(0), 0), + ) % icons.length; + return icons[fallbackIndex]; }; const iconImage = leadingImageSrc ?? getIconImage(post.slug); const iconAlt = leadingImageAlt ?? `Icon for ${post.frontmatter.title}`; - // Format date const formattedDate = new Date(post.frontmatter.date).toLocaleDateString( "en-US", { @@ -60,10 +70,9 @@ const ContentContainerContainer = memo( }, ); - // Choose styling based on size prop const containerClasses = size === "xs" - ? "relative z-20 h-full flex flex-col gap-[var(--measures-spacing-012)]" + ? "relative z-20 flex h-full flex-col gap-[var(--measures-spacing-012)]" : "relative z-20 h-full flex flex-col gap-[var(--measures-spacing-012)] sm:gap-[var(--measures-spacing-016)] md:gap-[18px] lg:gap-[var(--measures-spacing-024)]"; const contentGapClasses = @@ -78,7 +87,7 @@ const ContentContainerContainer = memo( const titleClasses = size === "xs" - ? `font-bricolage font-medium text-[18px] leading-[120%] transition-colors ${titleColor}` + ? `font-bricolage font-medium text-[18px] leading-[22px] transition-colors ${titleColor}` : `font-bricolage font-medium text-[18px] leading-[120%] sm:text-[24px] sm:leading-[24px] md:text-[32px] md:leading-[110%] lg:text-[44px] lg:leading-[110%] xl:text-[64px] xl:leading-[110%] transition-colors ${titleColor}`; const descriptionClasses = @@ -88,12 +97,12 @@ const ContentContainerContainer = memo( const authorClasses = size === "xs" - ? `font-inter font-normal text-[10px] leading-[14px] ${bodyColor}` + ? `overflow-hidden text-ellipsis whitespace-nowrap font-inter font-normal text-[10px] leading-[14px] ${bodyColor}` : `font-inter font-normal text-[10px] leading-[14px] md:text-[12px] md:leading-[16px] lg:text-[14px] lg:leading-[20px] xl:text-[18px] xl:leading-[130%] ${bodyColor}`; const dateClasses = size === "xs" - ? `font-inter font-normal text-[10px] leading-[14px] ${bodyColor}` + ? `overflow-hidden text-ellipsis whitespace-nowrap font-inter font-normal text-[10px] leading-[14px] ${bodyColor}` : `font-inter font-normal text-[10px] leading-[14px] md:text-[12px] md:leading-[16px] lg:text-[14px] lg:leading-[20px] xl:text-[18px] xl:leading-[130%] ${bodyColor}`; return ( diff --git a/app/components/content/ContentContainer/ContentContainer.view.tsx b/app/components/content/ContentContainer/ContentContainer.view.tsx index e5489b8..f837400 100644 --- a/app/components/content/ContentContainer/ContentContainer.view.tsx +++ b/app/components/content/ContentContainer/ContentContainer.view.tsx @@ -20,7 +20,7 @@ function ContentContainerView({ return (
{/* Content Container - gap between icon and text */}
@@ -45,8 +45,7 @@ function ContentContainerView({
- {/* Metadata Container - horizontal with 8px gap */} -
+
{/* Author Name */} {post.frontmatter.author} diff --git a/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.container.tsx b/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.container.tsx index 06cf0f5..673412a 100644 --- a/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.container.tsx +++ b/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.container.tsx @@ -1,13 +1,23 @@ "use client"; +/** + * Figma: "Components" / ContentThumnailTemplate (19614-14838, 19041-13415). + * Vertical 260×390 (19060-15787); horizontal 320×225.5 (19041-13550). + */ import { memo } from "react"; import { getAssetPath, ASSETS } from "../../../../lib/assetUtils"; import ContentThumbnailTemplateView from "./ContentThumbnailTemplate.view"; import type { ContentThumbnailTemplateProps } from "./ContentThumbnailTemplate.types"; const ContentThumbnailTemplateContainer = memo( - ({ post, className = "", variant: variantProp = "vertical" }) => { + ({ + post, + className = "", + variant: variantProp = "vertical", + sizing: sizingProp = "fluid", + }) => { const variant = variantProp; + const sizing = sizingProp; // Get article-specific background image from frontmatter const getBackgroundImage = ( post: ContentThumbnailTemplateProps["post"], @@ -42,6 +52,7 @@ const ContentThumbnailTemplateContainer = memo( post={post} className={className} variant={variant} + sizing={sizing} backgroundImage={backgroundImage} /> ); diff --git a/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.types.ts b/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.types.ts index f487c30..a7cf2d8 100644 --- a/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.types.ts +++ b/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.types.ts @@ -2,6 +2,8 @@ import type { BlogPost } from "../../../../lib/content"; export type ContentThumbnailTemplateVariantValue = "vertical" | "horizontal"; +export type ContentThumbnailTemplateSizingValue = "fluid" | "fixed"; + export interface ContentThumbnailTemplateProps { post: BlogPost; className?: string; @@ -9,6 +11,10 @@ export interface ContentThumbnailTemplateProps { * Content thumbnail variant. */ variant?: ContentThumbnailTemplateVariantValue; + /** + * fluid — fill parent (Learn grid). fixed — Figma px dimensions (Related Articles). + */ + sizing?: ContentThumbnailTemplateSizingValue; slugOrder?: string[]; } @@ -16,5 +22,6 @@ export interface ContentThumbnailTemplateViewProps { post: BlogPost; className: string; variant: "vertical" | "horizontal"; + sizing: ContentThumbnailTemplateSizingValue; backgroundImage: string; } diff --git a/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.view.tsx b/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.view.tsx index 0fe1e15..755ccd3 100644 --- a/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.view.tsx +++ b/app/components/content/ContentThumbnailTemplate/ContentThumbnailTemplate.view.tsx @@ -7,55 +7,95 @@ function ContentThumbnailTemplateView({ post, className, variant, + sizing, backgroundImage, }: ContentThumbnailTemplateViewProps) { if (variant === "vertical") { + if (sizing === "fixed") { + return ( + +
+
+ {/* eslint-disable-next-line @next/next/no-img-element */} + +
+
+ +
+
+ + ); + } + return ( -
- {/* Background SVG - fills container with maintained aspect */} +
{/* eslint-disable-next-line @next/next/no-img-element */} {`Background - {/* Gradient overlay for better text readability */} -
- - {/* Content Section - positioned within the padding constraints */} - +
+ +
+
+ + ); + } + + if (sizing === "fixed") { + return ( + +
+
+ {/* eslint-disable-next-line @next/next/no-img-element */} + +
+
+ +
); } - // Horizontal variant return ( -
- {/* Background SVG - sized to fit the 320x225.5 container exactly */} +
{/* eslint-disable-next-line @next/next/no-img-element */} {`Background - {/* Gradient overlay */} -
- - {/* Content - positioned within the padding constraints */} - +
+ +
); diff --git a/app/components/sections/RelatedArticles/RelatedArticles.view.tsx b/app/components/sections/RelatedArticles/RelatedArticles.view.tsx index 4a74062..4c03265 100644 --- a/app/components/sections/RelatedArticles/RelatedArticles.view.tsx +++ b/app/components/sections/RelatedArticles/RelatedArticles.view.tsx @@ -62,7 +62,7 @@ export function RelatedArticlesView({ )} - {/* Horizontal Articles Row - Carousel on mobile, Scrollable slider on desktop */} + {/* Horizontal Articles Row - Carousel on mobile, scrollable slider on desktop */}
(
diff --git a/content/blog/avoiding-burnout-sustainability-in-the-ruins.md b/content/blog/avoiding-burnout-sustainability-in-the-ruins.md new file mode 100644 index 0000000..0855e8b --- /dev/null +++ b/content/blog/avoiding-burnout-sustainability-in-the-ruins.md @@ -0,0 +1,75 @@ +--- +title: "Avoiding Burnout: Sustainability in the Ruins" +description: "Building a practice of resistance that doesn't consume you" +author: "Author name" +date: "2025-08-12" +related: + - "resolving-active-conflicts" + - "integrating-new-members-without-dilution" + - "how-chaos-concentrates-control" +thumbnail: + vertical: "avoiding-burnout-sustainability-in-the-ruins-vertical.svg" + horizontal: "avoiding-burnout-sustainability-in-the-ruins-horizontal.svg" +background: + color: "#EDCC8F" +--- + +The pattern repeats itself across every worker coop, mutual aid network, and organizing project. Someone who was essential suddenly stops showing up. They apologize, say they need to step back, talk about self-care and boundaries. Everyone nods sympathetically. The work gets redistributed to whoever's left. Six months later, someone else burns out. The cycle continues. + +We've learned to treat burnout as an individual problem requiring individual solutions. Take a break. Practice self-care. Set boundaries. Prioritize your mental health. This framing is everywhere, and it's almost entirely useless. Not because self-care is bad, but because it locates the problem in the individual rather than in the conditions that produce exhaustion as a constant state. + +Burnout in activist spaces isn't a personal failure. It's a political symptom. Specifically, it's what happens when capitalist temporality infiltrates movements that claim to oppose capitalism. + +Capitalist time operates in resistance spaces in a specific way: endless acceleration, perpetual crisis, the elimination of rest as a category. There's always more to do, more to respond to, more urgency. The logic of productivity infects everything. If you're not constantly doing, you're not committed. If you're not sacrificing yourself, you don't really care. + +This temporality gets imported wholesale into resistance movements. The world is burning, people are suffering, we can't afford to slow down. Anyone who suggests limits or sustainability gets accused of not understanding the urgency. The person who works themselves into the ground becomes the model of commitment. + +The result is predictable. Your most dedicated people destroy themselves. They leave, taking years of knowledge and relationships with them. New people step up, replicate the same patterns, and burn out in turn. The movement doesn't grow, it churns. Meanwhile, the systems you're fighting remain perfectly intact, staffed by people working forty-hour weeks with healthcare and retirement plans. + +This isn't strategy, it's self-sabotage dressed up as virtue. And it will continue until we build structures that make sustainability possible rather than treating it as a personal responsibility. + +Rotation as structure means that if the same people are always doing the critical work, burnout isn't a risk, it's a timeline. You're just waiting to see who collapses first. + +This feels wrong to people raised on the myth of indispensability. What if the new person isn't as good at it? What if things slip? These are real concerns, but they reveal the problem. If your group can't survive someone rotating out of a role, you don't have an organization, you have a house of cards. + +Rotation forces knowledge distribution. The person leaving a role has to document what they did and train their replacement. This is when you discover that half of what made something work was informal knowledge living in one person's head. Get it out of their head. Write it down. Make it teachable. + +Rotation also prevents the accumulation of informal power. The person who's always been the treasurer starts to feel ownership over financial decisions. The person who always coordinates meetings starts shaping what gets discussed. This isn't malicious, it's structural. Rotation disrupts it. + +Set the schedule in advance. You're not rotating people out because they're failing, you're rotating them because that's how the system works. This removes the stigma and the guilt. Everyone knows from the start that roles are temporary. + +Documentation isn't optional, it's infrastructure. Every role needs a guide: what it involves, how to do it, what to watch out for. When someone learns something important, it gets added to the guide. When someone rotates out, they update it with everything they learned. + +Run regular retrospectives, not as therapy but as knowledge capture. Every few months, sit down and discuss what worked, what didn't, what you learned. Write it down. This becomes your institutional memory, accessible to anyone who needs it. + +The person who's leaving needs to do a handoff. Not just "here's the password," but sitting down with their replacement for however long it takes to transfer what they know. This is part of the role, not an optional kindness. If you can't make time for handoffs, you can't actually rotate roles. + +Limiting scope is something groups avoid because it feels like admitting defeat, but here it is: you cannot do everything. Trying to do everything guarantees you'll do nothing well and burn out everyone involved. + +Define what you're actually trying to accomplish. Be specific. Not "fight capitalism" but "provide food assistance to fifty families in the neighborhood" or "support workers organizing in the service industry" or "maintain this piece of software that solves this specific problem." Scope that's too broad becomes an excuse for unlimited work. + +When something falls outside that scope, the answer is no. Not "we'll try to fit it in," not "maybe later," but no. This is impossible for people who've internalized the idea that saying no means you don't care. You have to care selectively or you'll care yourself into complete ineffectiveness. + +Urgency is constant under capitalism. There will always be another crisis, another person who needs help, another fight that demands attention. If your response is always yes, you're not building a movement, you're building a machine that consumes people. + +Rest as strategy is the hardest thing to accept because rest isn't something you earn after the work is done. The work is never done. Rest has to be structural, built into how you operate, or it won't happen. + +Schedule breaks into your calendar the same way you schedule meetings. Not "take a break when you need it" no one ever needs it until they're already destroyed. Monthly week-long breaks where nothing happens. Quarterly weekend retreats that aren't about work. Built-in downtime between major projects. + +Normalize people being unavailable. If someone says they can't take something on, that's the end of the discussion. No guilt, no pressure, no subtle implications that they're not committed enough. The person who protects their capacity is more valuable than the person who destroys themselves. + +Building structures for sustainability isn't about being nice to ourselves. It's about building movements that can actually win. That means rotation so knowledge spreads and no one becomes indispensable. That means preserving institutional memory so learning accumulates. That means limiting scope so the work remains possible. That means treating rest as strategy, not weakness. + +Rotation means no role is permanent. The person coordinating meetings, managing the budget, maintaining relationships with allied groups, running the communication channels all of it rotates on a set schedule. Six months, a year, whatever makes sense for the role and the group. + +Preserving institutional memory becomes critical when someone burns out and leaves, because they take everything they learned with them. How to navigate specific relationships. What strategies failed before. Why certain decisions were made. The new person starts from zero, makes the same mistakes, learns the same lessons. The group never accumulates knowledge, it just cycles through individuals. + +Maintain a history document. Not meeting minutes, but narrative. Why did you start focusing on this issue? What was the context for that major decision? What conflicts came up and how did you work through them? New people need this to understand why things are the way they are. + +Set actual limits on meeting times, on how many projects you take on, on what you expect from participants. Two hours for meetings, not whenever people run out of steam. Three active projects, not whatever comes up. One meeting per week, not whenever something urgent appears. + +Celebrate the person who steps back before they burn out, not the person who pushes through until they collapse. Right now we do the opposite. We venerate sacrifice and treat boundaries as suspect. This has to invert. + +Why this matters is clear: treating burnout as individual failure serves power perfectly. It ensures that resistance movements constantly lose their most experienced people. It prevents the accumulation of knowledge and relationships. It makes organizing seem impossible to anyone with responsibilities or limitations. It turns politics into a game that only the young, unencumbered, or self-destructive can play. + +The alternative is what we have now: an endless cycle of people burning bright and flaming out, while the systems we oppose continue grinding forward, staffed by people who go home at five and take vacations. We can do better. We have to. diff --git a/content/blog/building-community-trust.md b/content/blog/building-community-trust.md deleted file mode 100644 index f664b7f..0000000 --- a/content/blog/building-community-trust.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: "Sample: Building Community Trust" -description: "Strategies for fostering trust, transparency, and accountability in community organizations" -author: "Author name" -date: "2025-04-20" -related: - [ - "resolving-active-conflicts", - "operational-security-mutual-aid", - "making-decisions-without-hierarchy", - ] -thumbnail: - vertical: "building-community-trust-vertical.svg" - horizontal: "building-community-trust-horizontal.svg" -background: - color: "#EDCC8F" ---- - -Trust is the foundation of any successful community organization. Without it, even the best structures and processes will struggle to function effectively. Building and maintaining trust requires intentional effort, clear communication, and consistent follow-through on commitments. - -One key element of building trust is transparency. When community members understand how decisions are made, where resources go, and what challenges the organization faces, they're more likely to feel invested and supportive. This doesn't mean sharing every detail, but it does mean being open about the big picture and the reasoning behind important choices. - -Another crucial factor is accountability. When people make mistakes or fail to follow through on commitments, there need to be clear, fair processes for addressing these issues. This might involve mediation, restorative justice practices, or other approaches that focus on learning and repair rather than punishment. - -Regular communication also plays a vital role. Whether through newsletters, community meetings, or informal conversations, keeping people informed about what's happening helps prevent misunderstandings and builds a sense of shared purpose. It's especially important to communicate both successes and challenges honestly. - -Finally, trust is built through consistent action over time. When community members see that the organization follows through on its promises and treats people fairly, even in difficult situations, trust grows stronger. This consistency creates a foundation that can weather conflicts and challenges when they inevitably arise. diff --git a/content/blog/digital-mediation-and-the-death-of-nuance.md b/content/blog/digital-mediation-and-the-death-of-nuance.md new file mode 100644 index 0000000..34a33ef --- /dev/null +++ b/content/blog/digital-mediation-and-the-death-of-nuance.md @@ -0,0 +1,77 @@ +--- +title: "Digital Mediation and the Death of Nuance" +description: "How corporate platforms undermine solidarity and what to build instead" +author: "Author name" +date: "2025-08-18" +related: + - "operational-security-mutual-aid" + - "how-chaos-concentrates-control" + - "knowledge-management-and-institutional-amnesia" +thumbnail: + vertical: "digital-mediation-and-the-death-of-nuance-vertical.svg" + horizontal: "digital-mediation-and-the-death-of-nuance-horizontal.svg" +background: + color: "#E2EFFF" +--- + +This isn't neutral. The medium shapes the message, and algorithmic mediation shapes how we relate to each other in ways that actively undermine the solidarity we're trying to build. + +Watch what happens in a group chat when someone raises a contentious point. The conversation accelerates. People respond immediately, reacting to the last message without absorbing the whole thread. Nuance disappears. Someone misreads tone, someone else gets defensive, and within twenty minutes you've got a full-blown conflict that wouldn't have happened in a room together. + +This isn't because people are worse online. It's because the platform is designed to encourage rapid response over thoughtful engagement. The notification pulls you back in. The threading makes it easy to miss context. The lack of physical presence removes all the social cues that normally moderate conflict. The result is that digital platforms produce a specific kind of politics: reactive, fragmented, prone to escalation. + +Then there's the algorithm. Even in platforms that claim not to use algorithmic feeds, the structure determines what you see. The most active conversations rise to the top. The people who post most shape the space. The quiet person who thinks carefully before speaking gets buried. Leadership emerges not from wisdom or trust but from who has time to be constantly online. + +Worse, these platforms are surveillance infrastructure. Every conversation gets harvested for data. Your organizing discussions train the same AI systems being sold to police departments. Even the encrypted platforms require trusting that encryption holds and that the company won't change the terms. + +The fundamental problem is this: you're trying to build alternative structures for human coordination using tools built by and for capitalism. The contradiction isn't incidental, it's definitional. + +There's a move happening right now where the right claims technology as their territory. Tech becomes associated with libertarian fantasies, surveillance capitalism, and fascist aesthetics of efficiency. The response from much of the left is retreat: reject technology, return to analog, treat digital tools as inherently corrupting. + +This is a catastrophic mistake. Technology isn't neutral, but it isn't predetermined either. It's a tool, and tools can be built differently depending on who builds them and for what purpose. Ceding technology to techno-fascists means ceding the future. We can't organize a post-capitalist world using only the tools of the past. + +The question isn't whether to use technology but what kind of technology to build and how to use it. This means understanding that the platforms we rely on weren't designed for us and won't serve our purposes. It also means recognizing that alternatives exist and more can be created. + +Nathan Schneider's work on digital democracy points toward what's possible: platforms owned and governed by their users rather than by shareholders. Platform cooperatives apply cooperative principles to digital infrastructure. The people using the tool control how it operates, how it develops, what happens to the data. + +This isn't hypothetical. Platform coops exist for ride-sharing, delivery work, freelancing, social media. They're small compared to their corporate competitors, but they demonstrate that different models are viable. A messaging platform owned by the organizing groups using it would make different design choices than Slack or Discord. It wouldn't optimize for engagement metrics. It would optimize for the things communities actually need: clarity, accessibility, privacy, sustainability. + +Building platform coops requires resources and technical knowledge, which many groups lack. But so does building anything else worth having. The question is whether we're willing to invest in infrastructure we actually control or whether we'll keep building movements on foundations owned by people actively hostile to our goals. + +The Secret Riso Project understands something crucial: print isn't obsolete, it's liberated. Risograph printing produces zines, posters, and pamphlets outside the surveillance apparatus. The medium is tangible, shareable, untrackable. You can't delete a zine from someone's shelf. You can't change what a poster says after it's wheat-pasted to a wall. + +Print works differently than digital communication. It's slower, more deliberate. You can't dash off a hot take on a riso-printed newsletter. The friction is a feature. It creates space for thought, for editing, for collective decision-making about what actually needs to be said. + +Print also reaches different people. Not everyone lives online. The person who won't join your Discord might read a zine left at the coffee shop. The neighborhood that needs organizing might not be checking Instagram, but they see the posters on their walk to work. + +The solution isn't to abandon digital tools entirely. It's to use them strategically while refusing to let them monopolize how you organize. Use encrypted messaging for urgent coordination, not for extended discussions. When a conversation needs nuance, move it to a meeting. When something's important enough to debate, debate it in person where you can see faces and hear tone. + +Use digital tools for logistics: scheduling, sharing documents, quick updates. Don't use them for conflict resolution, major decisions, or relationship building. Those require physical presence. + +The right wants you to believe that technology inevitably produces surveillance, hierarchy, and control. They want this because it justifies their vision of techno-fascism as the only possible future. Don't give them that. + +But it also means recognizing that no tool solves political problems. Platform coops won't create solidarity if the people using them haven't built it face-to-face. Print won't reach people if no one's doing the work of distribution. Technology, whether digital or analog, is only ever a tool for amplifying and supporting organizing work that happens between actual humans. + +Stop letting convenience dictate strategy. Yes, everyone's already on Facebook. That doesn't mean Facebook is where organizing should happen. Build spaces you control, even if it takes more work. Invest in hybrid infrastructure: print capabilities, secure digital tools, physical meeting spaces. Treat this as seriously as you treat any other resource question. + +Learn about alternatives. Research platform coops, explore democratic technology projects, understand what's possible beyond corporate platforms. Support these projects with money and labor when you can. Develop technical literacy within your group. Not everyone needs to code, but someone should understand how your tools work and what the alternatives are. + +The anti-social network isn't about rejecting technology. It's about refusing to let corporate platforms mediate all human connection and political organizing. It's about building tools that serve us rather than extract from us. It's about remembering that the most important networks are the ones between actual people, in actual space, building actual power together. + +Your organizing group lives in a Discord server. Your mutual aid network coordinates through a Facebook group. Your worker coop runs on Slack. Every conversation, every decision, every conflict gets filtered through platforms designed by corporations to maximize engagement, which is just a polite word for addiction. + +The cooperative model also solves the governance problem. When users control the platform, they can decide collectively what features to build, what data to collect, how to handle moderation. This is democratic technology: tools that serve their users rather than extracting value from them. + +Use print for things that matter: analysis that took time to develop, information the community needs, calls to action, documentation of victories and lessons. Use it to create physical artifacts of your work that persist beyond the lifespan of any platform. + +Maintain analog systems alongside digital ones. Keep paper records. Print important documents. Create physical spaces where your group exists outside the digital panopticon. Host regular in-person gatherings, not as special events but as the default. + +Build your own tools when you can. Use platform coops. Support projects creating democratic alternatives to corporate platforms. Recognize that building infrastructure is organizing work, not a distraction from organizing. + +Produce print materials regularly. Zines that explain your analysis, posters that announce actions, newsletters that keep people informed. Make them beautiful. Make them worth keeping. Make them shareable outside digital networks. + +Technology is shaped by the social relations of its production. Capitalist technology serves capitalist ends. Cooperative technology can serve cooperative ends. The tools we build and how we build them are political choices. + +This means investing in democratic alternatives. It means learning technical skills or supporting people who have them. It means treating digital infrastructure as seriously as physical infrastructure. It means refusing to accept that organizing must happen on terms set by corporations. + +Use print strategically and beautifully. Make things people want to keep and share. Use risograph, xerox, whatever you have access to. The Secret Riso Project shows that small-scale print production can be both accessible and powerful. diff --git a/content/blog/how-chaos-concentrates-control.md b/content/blog/how-chaos-concentrates-control.md new file mode 100644 index 0000000..59c61b0 --- /dev/null +++ b/content/blog/how-chaos-concentrates-control.md @@ -0,0 +1,75 @@ +--- +title: "How Chaos Concentrates Control" +description: "How to limit informal hierarchies inevitably emerging in horizontal groups" +author: "Author name" +date: "2025-08-15" +related: + - "making-decisions-without-hierarchy" + - "resolving-active-conflicts" + - "digital-mediation-and-the-death-of-nuance" +thumbnail: + vertical: "how-chaos-concentrates-control-vertical.svg" + horizontal: "how-chaos-concentrates-control-horizontal.svg" +background: + color: "#C8E6F0" +--- + +Every failing collective tells itself the same story: we don't need formal structures because we trust each other. We're not like those hierarchical organizations. We make decisions organically, by consensus, through the natural flow of discussion. We don't have leaders. + +This is a fantasy. Worse, it's a fantasy that actively produces the hierarchies it claims to prevent. + +Here's what actually happens in structureless groups. Decisions get made, but no one can say exactly how or by whom. Some people's opinions carry more weight, but there's no acknowledged reason why. Information flows through informal networks, which means who you know determines what you know. Conflicts fester because there's no legitimate process for working through them. New members can't figure out how anything works because there's nothing to figure out, just vibes and unspoken norms. + +The group congratulates itself on being non-hierarchical while a small clique makes all the real decisions over drinks after meetings. This isn't an accident or a failure of the group's principles. This is what structurelessness produces. + +Jo Freeman named this decades ago: the tyranny of structurelessness. When you refuse to create explicit structures, you don't eliminate power. You make power invisible and therefore unaccountable. The hierarchies that emerge are based on personal relationships, charisma, free time, and existing social capital. In other words, they reproduce all the inequalities you were supposedly trying to escape. + +The solution isn't to embrace traditional hierarchy. It's to build transparent structures that acknowledge what every group actually needs: ways to make decisions, coordinate action, resolve conflicts, and distribute responsibility. Structures aren't the enemy. Hidden structures are. + +Explicit decision-making is the first step. Stop pretending decisions emerge organically. They don't. Someone proposes something, some people support it, and somehow it becomes what the group does. Make that process visible. + +Decide how you make decisions. Consensus? Majority vote? Consensus with fallback to voting? Different methods for different types of decisions? It doesn't matter which you choose as long as everyone knows what it is. + +Set time limits for discussion. This sounds bureaucratic but it's the opposite. Without limits, discussions go until the people with the most stamina or free time get their way. That's not democracy, that's a war of attrition. + +Make decisions in meetings, not in back channels. If it affects the group, it gets discussed where the group can hear it. The post-meeting group chat can't be where real decisions happen. + +Rotating responsibilities prevents the accumulation of informal power. Your group has functions that need doing: taking notes, managing money, communicating with outside groups, maintaining your tools or space, coordinating meetings. Pretending these aren't positions of influence is delusional. + +Name the roles explicitly. Note-taker, treasurer, communications coordinator, whatever you need. Write down what each role does. This isn't about creating a bureaucracy, it's about making power visible so it can be shared. + +When someone rotates out, they document what they did and train their replacement. Knowledge can't be hoarded. If only one person understands the finances or the website or the coalition relationships, they have structural power whether you acknowledge it or not. + +Keep records. Meeting notes, decisions made, rationales discussed. This isn't paranoia, it's memory. Without records, the people who were there control the narrative. + +Share context proactively. When discussing something that builds on previous decisions or ongoing situations, take a minute to recap for people who weren't there. Otherwise participation becomes impossible for anyone who can't attend everything. + +Legitimate conflict resolution matters because conflicts will happen. In structureless groups, they get resolved through whoever has more social capital, who can mobilize their friends, who's willing to make things uncomfortable until they get their way. This is violence pretending to be harmony. + +Create a process. When there's a conflict between members, what happens? Mediation by agreed-upon people? A structured conversation using specific guidelines? Bringing it to the full group? Figure it out before you need it. + +Make it legitimate. Everyone agrees this is how conflicts get worked through, which means everyone agrees to accept the outcome even if they don't like it. Without legitimacy, people just route around the process until they get what they want. + +The process should be separate from personal relationships. Your closest friend in the group shouldn't be mediating your conflicts. That's not neutrality, that's just a different kind of power play. + +Accountability structures ensure that actions have consequences. If someone consistently doesn't do what they committed to, what happens? If someone treats others badly, what's the recourse? In structureless groups, the answer is usually nothing, or else someone gets quietly frozen out through social pressure. + +Decide what accountability looks like. Checking in on commitments? Having conversations when things slip? Clear consequences for patterns of behavior that harm the group? This feels uncomfortable because we're taught that structure equals oppression. But without accountability, you just have unaccountable power. + +Make it proportional and restorative when possible. The point isn't punishment, it's maintaining the group's capacity to function and keeping people safe. But sometimes people need to leave, and there should be a legitimate process for that too. + +Why this matters comes down to power. The fantasy of structurelessness serves power in two ways. First, it allows informal elites to consolidate control while claiming there's no hierarchy. Second, it makes groups dysfunctional, which is convenient for anyone who benefits from the absence of organized opposition. + +When your worker coop can't make decisions, when your mutual aid network splinters over unresolved conflicts, when your organizing committee burns out because a few people do everything, that's not a failure of the people involved. That's structurelessness working exactly as it's designed to. + +The choice isn't between structure and freedom. It's between explicit structures that can be challenged and changed, and implicit structures that operate in the shadows. One enables democracy. The other prevents it. + +Build structures. Make them visible. Change them when they don't work. That's not selling out, that's how you build something that lasts. + +Name who can bring proposals and how they get on the agenda. Otherwise the people who are most comfortable speaking up or who talk to the right people beforehand set the agenda by default. + +Rotate them. Six months, a year, whatever makes sense. This prevents anyone from accumulating informal power through being the only person who knows how things work. It also distributes skills across the group. + +Information access is crucial because hidden information is hidden power. If discussions happen in private chats, if decisions get made in informal hangouts, if context lives in the heads of a few long-timers, you have an invisible hierarchy. + +Make information accessible. Use shared documents, public channels, whatever works. The point is that anyone in the group can access what they need to participate fully. diff --git a/content/blog/integrating-new-members-without-dilution.md b/content/blog/integrating-new-members-without-dilution.md new file mode 100644 index 0000000..94db558 --- /dev/null +++ b/content/blog/integrating-new-members-without-dilution.md @@ -0,0 +1,53 @@ +--- +title: "Integrating New Members Without Dilution" +description: "How to Bring New People In Without Everything Falling Apart" +author: "Author name" +date: "2025-08-05" +related: + - "making-decisions-without-hierarchy" + - "knowledge-management-and-institutional-amnesia" + - "avoiding-burnout-sustainability-in-the-ruins" +thumbnail: + vertical: "integrating-new-members-without-dilution-vertical.svg" + horizontal: "integrating-new-members-without-dilution-horizontal.svg" +background: + color: "#F5D4C8" +--- + +Your worker coop is finally stable. Your mutual aid group actually delivers. Your open source project has momentum. People show up, work gets done, meetings function. You've built something that works, which means people want to join. That's where the problem starts. + +Every new person threatens what you've built. Not because they're incompetent, but because integration takes work. Someone has to explain how things operate, translate the shared references, bring them up to speed. Usually that someone is whoever has spare energy, which means your best people become full-time onboarding machines while actual work suffers. + +The usual response is to stop letting people in. But a collective that can't grow is already dying. Your members will move, burn out, lose interest. If you haven't figured out how to integrate new people, you're counting down to collapse. + +You need a system. Here's what works. + +The buddy system means pairing every new person with an established member for three months. The buddy meets them before the first meeting to explain basics: who's who, what the projects are, how meetings actually run. They sit together during meetings for real-time context. They check in between meetings to answer questions. + +This works because what makes a group function can't be written down. When is it okay to disagree? How do decisions actually get made? What does "I'll look into that" really mean? The buddy translates this. + +Critical part: rotate who does it. If it's always the same people, you burn them out and create an insider class. Everyone around for six months should buddy someone. + +Splitting when you get big becomes necessary because past twenty people, the dynamics change. Intimacy that worked for a small group can't cover dozens. The culture doesn't scale, it just thins out. + +This requires the original core to accept that different groups will operate differently. That's not a threat, that's how things spread. + +Growth means change, and you can't add people without things changing. New people bring new perspectives and energy. If your group can't absorb that, the problem isn't them. + +These practices make change manageable. They create structures so integration doesn't depend on heroes working themselves to death. The alternative is slow collapse: your best people burn out, new people drift away, eventually no one's left. + +You need more people. You need them to actually become part of what you're building. This is how. + +Staged responsibility means not giving new people important roles immediately. Map which tasks need deep organizational knowledge and which just need someone willing. Coalition negotiations? That needs context. Social media? Less so. Note-taking? Anyone can do it. + +Month one: observe and take simple tasks. Show up, help with setup, take notes. Month two: join working groups, contribute to discussions, take on contained tasks. Month three onward: more autonomy as understanding deepens. + +Make this visible. Otherwise committed people stay peripheral forever because no one thought to offer them more. + +Making knowledge redundant matters because if one person holds crucial knowledge, you have a failure point. When they leave, the knowledge leaves. + +Document processes, but recognize documentation misses most of what matters. Meeting notes say you decided X, not why everyone was skeptical or how you got there. That lives in stories. + +Every few months, talk through key moments: actions that worked, conflicts resolved, important decisions. Make sure newer members hear it. The informal spaces matter too. Pre-meeting chat, post-action talk, group messages. This is where people learn what actually gets valued. + +Split into smaller working groups with real autonomy. Five to ten people, focused on specific work. They coordinate through assemblies but operate independently. A new person joining a six-person group can be effectively integrated. A new person joining a forty-person meeting just gets lost. diff --git a/content/blog/knowledge-management-and-institutional-amnesia.md b/content/blog/knowledge-management-and-institutional-amnesia.md new file mode 100644 index 0000000..dcab8ba --- /dev/null +++ b/content/blog/knowledge-management-and-institutional-amnesia.md @@ -0,0 +1,63 @@ +--- +title: "Knowledge Management and Institutional Amnesia" +description: "Preserving what we learn without surveillance infrastructure" +author: "Author name" +date: "2025-08-20" +related: + - "integrating-new-members-without-dilution" + - "operational-security-mutual-aid" + - "avoiding-burnout-sustainability-in-the-ruins" +thumbnail: + vertical: "knowledge-management-and-institutional-amnesia-vertical.svg" + horizontal: "knowledge-management-and-institutional-amnesia-horizontal.svg" +background: + color: "#D4F0E0" +--- + +Someone leaves the group and takes three years of operational knowledge with them. Not because they're selfish, but because that knowledge lived in their head and in message threads that have already disappeared. The new person trying to figure out how intake works has to reconstruct the process from fragments and half-remembered explanations. Six months later, someone else leaves and the cycle repeats. + +This is the tax encrypted organizing pays. Signal, Matrix, and similar tools protect vulnerable people from surveillance. The ephemerality is the point. But groups need institutional memory to function. You can't rediscover how to verify requests or coordinate emergency response every time your membership turns over. The energy cost is unsustainable. + +This isn't a personal failure. It's what happens when the tools we use for privacy conflict with the requirements of institutional memory. The question isn't whether to use encrypted messaging, it's how to preserve knowledge without compromising security. + +Signal's disappearing messages and lack of cloud storage are features, not bugs. They protect people. But cooperatives and organizing groups need to document procedures: how intake works, how resources get distributed, how conflicts get resolved, what to do in emergencies. The solution isn't abandoning encrypted tools. It's building complementary systems that respect both privacy and sustainability. + +The most effective approach separates operational communication from institutional documentation. Signal, Matrix, or whatever encrypted tool you use stays as the primary channel for real-time coordination, personal information, and sensitive discussions. Meanwhile, procedural knowledge that doesn't contain identifying information lives somewhere more permanent. + +For documentation, use password-protected wikis, encrypted cloud documents like those provided by Nextcloud cooperatives, or collaborative platforms like CryptPad that prioritize privacy. The commercial option is Notion, though it's surveillance infrastructure pretending to be helpful. Better to support cooperative alternatives when possible. + +Essential documentation includes onboarding guides for new volunteers explaining roles and expectations. Step-by-step protocols for common tasks like intake, verification, and distribution. Decision-making processes and governance structures so people understand how choices actually get made. Contact trees and escalation procedures for when things go wrong. Templates for common messages or forms so people aren't reinventing basic communications. Retrospectives on what worked and what didn't during major operations. + +This last one matters more than groups realize. After a big action or crisis response, you know what succeeded and what failed. Six months later, that knowledge has evaporated. Document it while it's fresh. Future members will thank you for not making them learn through the same painful trial and error. + +Knowledge management requires ongoing attention, which means it needs to be someone's responsibility. Designate a rotating documentation coordinator. Not a permanent position, because that creates the same knowledge hoarding problem you're trying to solve. Rotate every few months. The coordinator's job is capturing new procedures as they emerge and making sure documentation stays current. + +After significant operations or when processes change, hold brief documentation sessions. Thirty minutes where participants help update guides while details are still clear. This isn't bureaucracy, it's preventing the loss of hard-won understanding. + +Worker cooperatives face similar challenges. Member composition changes over time. If operational knowledge lives only in people's heads or in vanished message threads, every transition becomes a crisis. Documenting procedures isn't about control, it's about continuity. It's ensuring that the person who developed the accounting workflow or the member intake process doesn't become a single point of failure. + +The hardest part isn't technical, it's cultural. Groups that move fast and prioritize immediate needs often see documentation as bureaucracy. This is backwards. Documentation is care work. You're caring for future volunteers and the sustainability of your mission. When exhausted members can find clear procedures instead of piecing together information from scattered message threads, everyone benefits. + +Frame it as preparation for growth, not paranoia about decline. If your group succeeds, you'll need to onboard new people efficiently. If your project gains users, you'll need documentation they can actually use. If your cooperative expands, new members need to understand how things work. The alternative is that your most knowledgeable people become bottlenecks, explaining the same things over and over until they burn out. + +Consider what gets lost when documentation doesn't exist. The person who set up your verification process leaves. No one remembers exactly how it worked or why certain steps matter. You reinvent something inferior. The coop member who negotiated your supplier relationships retires. No one documented those connections or the reasoning behind vendor choices. You start from scratch. The developer who built a critical feature disappears. No one understands the architecture decisions. The technical debt accumulates. + +This pattern repeats endlessly in groups that don't take knowledge management seriously. It's not dramatic, it's grinding. Constant small losses of understanding that accumulate into major dysfunction. + +Tools matter here. CryptPad offers encrypted collaborative documents without corporate surveillance. Nextcloud can be run by cooperative hosting providers, keeping your data under collective control rather than corporate ownership. BookStack provides wiki functionality with granular permissions. Zulip offers persistent, threaded communication that's more searchable than Signal while still being open source. + +The point isn't specific tools, it's the principle: use encrypted ephemeral channels for sensitive operational communication, use persistent documentation systems for procedural knowledge, and make sure those systems align with your values around surveillance and ownership. + +Stop treating institutional memory as a luxury you'll get to when things calm down. Things won't calm down. The crisis is permanent under capitalism. Either you build systems that preserve knowledge despite constant turbulence, or you accept endless cycles of reinvention that exhaust everyone involved. + +Documentation isn't about bureaucracy. It's about refusing to let hard-won knowledge disappear when it could help the next person trying to do the work. It's about building organizations that can outlast any individual member. It's about sustainability in the ruins. + +The critical distinction: these repositories contain procedures, not personal data. Document the how of your work without including identifying information. How to verify requests, how to coordinate pickups, how to handle disputes. The process, not the people. + +Build redundancy into your system. Multiple members should have access to documentation repositories. Critical information should exist in at least two forms. If the wiki goes down or someone loses the password, you need backup. Consider quarterly reviews where coordinators check that documentation remains current and accessible. + +Not everything needs the same security level. Public-facing resources like volunteer signup information can live openly. Internal procedures might use password protection. Only information that could directly harm community members requires encryption. Create clear guidelines about what stays in encrypted channels versus what moves to documentation. + +Case-specific details stay in Signal. Personal information stays in Signal. Real-time sensitive coordination stays in Signal. General procedures, lessons learned, organizational structures can move to documentation. The line is whether the information could identify or harm specific people. If yes, it stays ephemeral. If no, it gets preserved. + +The open source model offers useful parallels here. Successful projects separate community discussion, which happens in ephemeral channels, from documentation, which lives in wikis and readme files. They have contribution guidelines, architecture decisions, and troubleshooting guides that persist. The knowledge exists independently of any individual contributor. When someone leaves, their specific conversations may disappear, but their contributions to understanding how the system works remain. diff --git a/content/blog/making-decisions-without-hierarchy.md b/content/blog/making-decisions-without-hierarchy.md new file mode 100644 index 0000000..bcce73e --- /dev/null +++ b/content/blog/making-decisions-without-hierarchy.md @@ -0,0 +1,25 @@ +--- +title: "Making decisions without hierarchy" +description: "A brief guide to collaborative nonhierarchical decision making" +author: "Author name" +date: "2025-08-01" +related: + - "resolving-active-conflicts" + - "integrating-new-members-without-dilution" + - "how-chaos-concentrates-control" +thumbnail: + vertical: "making-decisions-without-hierarchy-vertical.svg" + horizontal: "making-decisions-without-hierarchy-horizontal.svg" +background: + color: "#E8D4F4" +--- + +Many groups try to work without bosses, managers, or traditional leadership structures. But when no one's in charge, how do decisions actually get made? Non-hierarchical groups often rely on collective processes that prioritize trust, transparency, and shared responsibility. These approaches can take more time upfront, but they help build stronger, more equitable communities in the long run. + +One common method is consensus-based decision-making. In this approach, the goal isn't just to get majority agreement but to ensure that everyone can live with the outcome. Consensus doesn't mean everyone gets exactly what they want. It means no one is actively opposed. This usually requires open discussion, active listening, and a willingness to compromise. It also works best when the group has shared values and clear communication norms. + +Another option is to use roles or working groups that have specific scopes of responsibility, even if the group itself is flat. For example, one team might handle finances while another focuses on outreach. These roles can rotate or be chosen by the group, and decisions within those areas can be made autonomously, provided there's transparency and accountability back to the wider group. + +Tools also matter. Structured facilitation, shared agendas, and decision logs can keep the process from getting stuck or dominated by a few voices. Some groups use hand signals or colored cards during meetings to check for consensus or surface concerns. Others rely on asynchronous tools like polls, shared documents, or messaging platforms to give everyone a chance to weigh in. + +Non-hierarchical decision-making isn't about having no structure. It's about choosing structures that reflect the group's values and support participation. It takes intention and care, but done well, it creates space for more voices, deeper buy-in, and decisions that reflect collective wisdom rather than individual authority. diff --git a/content/blog/operational-security-mutual-aid.md b/content/blog/operational-security-mutual-aid.md index 601879f..e3c4bed 100644 --- a/content/blog/operational-security-mutual-aid.md +++ b/content/blog/operational-security-mutual-aid.md @@ -1,36 +1,33 @@ --- -title: "Sample: Operational Security for Mutual Aid" -description: "Tactics to protect members, secure communication, and prevent infiltration" +title: "Operational Security for Mutual Aid" +description: "Why protecting information isn't paranoia: it's care work in a hostile world" author: "Author name" -date: "2025-04-10" -related: ["resolving-active-conflicts", "making-decisions-without-hierarchy"] +date: "2025-08-10" +related: + - "resolving-active-conflicts" + - "digital-mediation-and-the-death-of-nuance" + - "knowledge-management-and-institutional-amnesia" thumbnail: vertical: "operational-security-mutual-aid-vertical.svg" horizontal: "operational-security-mutual-aid-horizontal.svg" -banner: - horizontal: "operational-security-mutual-aid-banner.svg" background: color: "#F4F3F1" --- -Mutual aid organizations face unique security challenges. Unlike traditional nonprofits, they often operate in politically sensitive environments and may be targets of surveillance, infiltration, or repression. This guide provides practical strategies for protecting your organization and its members. +We like to think of mutual aid as something pure, outside the machinery of state surveillance and corporate extraction. But the reality is messier. The moment you start organizing, you're generating data. Names, addresses, health conditions, immigration status, financial need. All of it stored somewhere, passed between people, vulnerable to exactly the kind of scrutiny that could harm the people you're trying to help. -Understanding the threat landscape is crucial before implementing security measures. External threats include surveillance by government or corporate entities, infiltration by agents or informants, legal or extralegal repression, and doxxing of members' personal information. Internal threats can include burnout leading to security lapses, inadvertent information sharing through gossip, poor communication creating vulnerabilities, and lack of training resulting in risky decisions. +Operational security isn't about cosplaying as revolutionaries or obsessing over encrypted everything. It's about recognizing that care work happens in a context where information itself can become a weapon. When you're distributing food to undocumented neighbors or running an underground HRT network, a single careless spreadsheet can translate directly into state violence. The risk isn't theoretical. -Secure communication forms the foundation of operational security. For digital communication, use Signal for sensitive conversations and avoid SMS for anything confidential. Consider Matrix for larger group communications and regularly update apps and devices. For email security, use encrypted services like ProtonMail or Tutanota, enable two-factor authentication, be cautious with attachments, and avoid discussing sensitive topics in email. On social media, use separate accounts for personal and organizational use, be mindful of location data in photos, don't post about future activities, and consider using pseudonyms. +The problem is that most mutual aid networks inherit their infrastructure from the nonprofit world or just use whatever's convenient. Google Sheets because everyone has access. Facebook groups because that's where people already are. Venmo because it's fast. Each of these choices makes perfect sense in isolation, but together they create a surveillance surface that would make any security professional wince. -For in-person communication, choose meeting locations carefully and be aware of your surroundings. Don't discuss sensitive topics in public and use code words when necessary. Keep physical documents secure, shred sensitive materials, don't leave notes in public places, and use secure storage for important files. +Start with the basics. Who actually needs to know what? Not everyone in your network needs access to intake forms with people's full legal names and why they need help. Compartmentalization isn't about mistrust, it's about minimizing exposure. If someone's phone gets seized or their account gets compromised, what can they give up? The less any single person knows, the safer everyone is. -Protecting information is crucial for member safety and organizational effectiveness. Classify data into public information (general organizational goals, public events, contact information for inquiries, educational materials), internal information (member contact details, meeting schedules, internal processes, financial information), and confidential information (personal details of vulnerable members, security procedures, legal strategies, sources of funding). Implement access control by limiting access based on need, using secure passwords and two-factor authentication, regularly reviewing who has access to what, and following a "need to know" principle. +Think about your tools. Signal is free and actually encrypted. Proton Mail costs nothing for basic use. There are alternatives to Google Drive that don't scan your files for content. These aren't difficult switches to make, but they require convincing people to change habits, which is always harder than the technical side. Frame it as part of the work, not an optional extra. If you're serious about care, you have to be serious about protection. -Physical security is equally important. For meeting spaces, choose neutral, accessible locations, avoid predictable patterns, consider multiple backup locations, and be aware of surveillance capabilities. During meetings, check for recording devices, ensure exits are accessible, have a security plan for disruptions, and know your legal rights. For events, assess potential risks, plan for different scenarios, coordinate with other organizations, and have legal observers present. During events, monitor for infiltrators, document any incidents, have medical support available, and know emergency procedures. +Data retention matters too. How long are you keeping intake forms? Why? Every piece of information you hold is a potential liability. Set up systems where sensitive data gets deleted on a schedule, not kept indefinitely because you might need it someday. You probably won't, and if you do, the risk of keeping it likely outweighs the benefit. -Member protection is paramount. For personal security, use strong, unique passwords, enable two-factor authentication, keep software updated, and be cautious with public WiFi. For physical safety, vary your routines, be aware of surveillance, trust your instincts, and have emergency contacts. Support systems should include recognizing signs of burnout, providing emotional support, connecting members with resources, and creating safe spaces for discussion. For legal support, know your rights, have legal contacts ready, document incidents, and support members facing legal issues. +And then there's the cultural piece, which is harder to solve with an app. Mutual aid attracts people who want to be helpful, who want to share and connect. That openness is beautiful but it's also a vulnerability. Teaching people to be thoughtful about what they post, what they photograph, who they name, requires ongoing conversation. It's not natural to most people raised in an attention economy where sharing everything is the default. -Organizational security requires systematic approaches. For structure and processes, use consensus-based decision making, document decisions securely, limit information to necessary people, and conduct regular security reviews. For financial security, use secure banking methods, keep financial records private, diversify funding sources, and conduct regular financial audits. Training and education should include regular security briefings, role-playing scenarios, updates on new threats, and individual security assessments. Legal education should cover knowing your rights, understanding local laws, legal observer training, and emergency legal procedures. +None of this is about becoming invisible or retreating into bunkers. It's about building practices that match the stakes. You can still be open, still be welcoming, still operate with trust at the center. But you do it with eyes open to the fact that the state is watching, that platforms will comply with subpoenas, that one mistake can unravel months of work and endanger real people. -Despite best efforts, infiltration can still occur. Warning signs include asking too many questions, pushing for sensitive information, creating division within the group, and unusual interest in security procedures. Response procedures should include documenting suspicious behavior, discussing concerns with trusted members, implementing additional security measures, and considering removing problematic individuals. After infiltration, assess what information was compromised, update security procedures, support affected members, and learn from the experience. - -Long-term security comes from building resilient organizations. Strong relationships are built through consistent action, supporting each other through challenges, creating multiple communication channels, and regular check-ins and support. Diversification means not relying on single points of failure, having multiple leaders and organizers, diverse funding sources, and various communication methods. Continuous improvement involves monthly security assessments, annual security audits, learning from incidents, and updating procedures. Adaptation requires staying informed about new threats, updating security measures, training new members, and sharing knowledge with allies. - -Operational security is not about paranoia—it's about practical protection that allows your organization to continue its important work safely and effectively. By implementing these strategies thoughtfully and consistently, you can create a secure foundation for your mutual aid efforts. Remember: security is everyone's responsibility, and it's better to be prepared than to react to a crisis. +Operational security is another form of solidarity. It says: your safety matters enough that we'll do the boring, annoying work of protecting it. It's not glamorous, but neither is most care work. And like all care work, it's political whether we admit it or not. diff --git a/content/blog/resolving-active-conflicts.md b/content/blog/resolving-active-conflicts.md index 8db30b4..793d0e2 100644 --- a/content/blog/resolving-active-conflicts.md +++ b/content/blog/resolving-active-conflicts.md @@ -4,22 +4,26 @@ description: "Practical steps for resolving conflicts while maintaining trust, c author: "Author name" date: "2025-04-15" related: - ["operational-security-mutual-aid", "making-decisions-without-hierarchy"] + - "operational-security-mutual-aid" + - "making-decisions-without-hierarchy" + - "avoiding-burnout-sustainability-in-the-ruins" thumbnail: vertical: "resolving-active-conflicts-vertical.svg" horizontal: "resolving-active-conflicts-horizontal.svg" -banner: - horizontal: "resolving-active-conflicts-banner.svg" background: color: "#E2EFFF" --- -Many groups strive to work without bosses, managers, or traditional leadership structures. But when no one's in charge, how do decisions get made? Non-hierarchical groups often rely on collective processes that prioritize trust, transparency, and shared responsibility. These approaches can take more time upfront, but they help build stronger, more equitable communities in the long run. +Conflict is not the enemy of mutual aid. It's an inevitable feature of any genuine collective effort. The sanitized fantasy of frictionless cooperation belongs to corporate team-building retreats, not to the real work of building alternative structures of care and support. When people come together with different experiences, needs, and visions, tension emerges. The question isn't how to avoid conflict, but how to metabolize it productively. -One common method is consensus-based decision-making. In this approach, the goal is not just to get majority agreement but to ensure that everyone can live with the outcome. Consensus doesn't mean everyone gets exactly what they want—it means no one is actively opposed. This usually requires open discussion, active listening, and a willingness to compromise. It also works best when the group has shared values and clear communication norms. +The neoliberal imagination wants us to believe that conflict signals failure, that properly functioning groups hum along without disagreement. This is ideological mystification. Real solidarity is forged through working through differences, not around them. -Another option is to use roles or working groups that have specific scopes of responsibility, even if the group itself is flat. For example, one team might handle finances while another focuses on outreach. These roles can rotate or be chosen by the group, and decisions within those areas can be made autonomously—provided there's transparency and accountability back to the wider group. +Any approach to conflict resolution needs to rest on three foundational pillars. First is structural clarity: clear, agreed-upon processes for decision-making, membership, and conflict resolution. Ambiguity breeds conflict. Second is political analysis. Most conflicts aren't about surface disagreements but deeper questions of power, resources, and strategy. Resolution must excavate these underlying dimensions. Third is collective responsibility. Conflict resolution isn't something that happens to a group. It's work the group does together. -Tools also matter. Structured facilitation, shared agendas, and decision logs can keep the process from getting stuck or dominated by a few voices. Some groups use hand signals or colored cards during meetings to check for consensus or surface concerns. Others rely on asynchronous tools like polls, shared documents, or messaging platforms to give everyone a chance to weigh in. +Mutual aid groups have various conflict resolution methods at their disposal, tools that can strengthen collective capacity and create structural options before conflicts escalate. Which methods work depends on material realities like group size, not abstract preferences about process. Consensus building works when groups can afford the time investment but breaks down under resource pressure or tight deadlines. Conflict workshops provide skill-building before tensions arise, though they're only as effective as people's willingness to actually use the tools. Supermajority voting (75-80%) balances efficiency with broad buy-in when consensus feels impossible. Facilitated group conversations create structured space for working through tensions collectively rather than letting them fester in side conversations. Facilitated negotiation brings in outside support when internal dynamics make resolution difficult, though finding truly neutral facilitators with political analysis can be challenging. Circle processes draw on indigenous practices to center relationship and accountability, particularly effective for addressing harm. Code of conduct enforcement establishes clear boundaries and consequences, essential for groups serious about creating safer spaces and after conflict. Direct negotiation between affected parties works when power dynamics are relatively balanced and people have conflict resolution skills. Conflict management councils create dedicated structures for handling disputes, drawing on the experience and long standing context of respected group members willing to take on this responsibility. -Non-hierarchical decision-making isn't about having no structure—it's about choosing structures that reflect the group's values and support participation. It takes intention and care, but done well, it creates space for more voices, deeper buy-in, and decisions that reflect collective wisdom, not just individual authority. +Building conflict resolution capacity isn't optional. It's infrastructure. Groups that wait until conflicts explode to think about resolution processes are like activists who wait until the police arrive to think about security culture. The methods you choose depend on your group's material conditions and scale, but the principle remains constant: conflict-capable groups are built, not born, through deliberate practice and structural preparation. The key is having multiple tools available and choosing strategically rather than defaulting to whatever feels most comfortable. Writing down the methods that your group has chosen to use can make it easy to choose what method might work best for your group when a situation arises. + +Capitalist competition trains us to see conflict as zero-sum. Mutual aid offers a different model: conflict as compost, messy organic matter that, when processed well, creates richer soil for collective flourishing. Most conflicts contain valuable information about what needs to change. They reveal hidden assumptions, unmet needs, and structural problems invisible during smooth times. Groups that learn to metabolize conflict develop stronger practices, deeper relationships, and more effective organizing. The goal isn't harmony but antifragility, groups that get stronger through stress rather than breaking under pressure. In a world designed to isolate us, learning to work through conflict together is both means and end. + +The revolution will not be conflict-free. But it can be prepared to manage conflicts before they inevitably arise. diff --git a/lib/assetUtils.ts b/lib/assetUtils.ts index 7140d8b..25444d5 100644 --- a/lib/assetUtils.ts +++ b/lib/assetUtils.ts @@ -68,6 +68,23 @@ export function guideBannerLogoArrowPath(): string { return "assets/shapes/guide-banner-logo-arrow.svg"; } +/** Per-article Tag mark for ContentContainer (Figma Tag frame 19600:15534). */ +export function contentBlogTagPath(slug: string): string { + return `/content/blog/${slug}-tag.svg`; +} + +/** Stable catalog slug order for icon fallbacks when a tag asset is missing. */ +export const CONTENT_CATALOG_SLUG_ORDER = [ + "resolving-active-conflicts", + "operational-security-mutual-aid", + "making-decisions-without-hierarchy", + "integrating-new-members-without-dilution", + "avoiding-burnout-sustainability-in-the-ruins", + "how-chaos-concentrates-control", + "digital-mediation-and-the-death-of-nuance", + "knowledge-management-and-institutional-amnesia", +] as const; + /** * Asset paths for common components */ diff --git a/lib/content.ts b/lib/content.ts index 2c2ceb1..3a36859 100644 --- a/lib/content.ts +++ b/lib/content.ts @@ -71,7 +71,9 @@ export function getBlogPostFiles(): string[] { try { const files = fs.readdirSync(contentDirectory); return files.filter( - (file) => file.endsWith(".md") || file.endsWith(".mdx"), + (file) => + (file.endsWith(".md") || file.endsWith(".mdx")) && + !file.startsWith("_"), ); } catch (error) { logger.error("Error reading blog content directory:", error); diff --git a/public/content/blog/avoiding-burnout-sustainability-in-the-ruins-horizontal.svg b/public/content/blog/avoiding-burnout-sustainability-in-the-ruins-horizontal.svg new file mode 100644 index 0000000..b2148f5 --- /dev/null +++ b/public/content/blog/avoiding-burnout-sustainability-in-the-ruins-horizontal.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/content/blog/avoiding-burnout-sustainability-in-the-ruins-tag.svg b/public/content/blog/avoiding-burnout-sustainability-in-the-ruins-tag.svg new file mode 100644 index 0000000..873505c --- /dev/null +++ b/public/content/blog/avoiding-burnout-sustainability-in-the-ruins-tag.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/public/content/blog/avoiding-burnout-sustainability-in-the-ruins-vertical.svg b/public/content/blog/avoiding-burnout-sustainability-in-the-ruins-vertical.svg new file mode 100644 index 0000000..374f144 --- /dev/null +++ b/public/content/blog/avoiding-burnout-sustainability-in-the-ruins-vertical.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/content/blog/building-community-trust-horizontal.svg b/public/content/blog/building-community-trust-horizontal.svg deleted file mode 100644 index ff6736d..0000000 --- a/public/content/blog/building-community-trust-horizontal.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/content/blog/building-community-trust-vertical.svg b/public/content/blog/building-community-trust-vertical.svg deleted file mode 100644 index 0b9aa36..0000000 --- a/public/content/blog/building-community-trust-vertical.svg +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/content/blog/digital-mediation-and-the-death-of-nuance-horizontal.svg b/public/content/blog/digital-mediation-and-the-death-of-nuance-horizontal.svg new file mode 100644 index 0000000..838dafd --- /dev/null +++ b/public/content/blog/digital-mediation-and-the-death-of-nuance-horizontal.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/content/blog/digital-mediation-and-the-death-of-nuance-tag.svg b/public/content/blog/digital-mediation-and-the-death-of-nuance-tag.svg new file mode 100644 index 0000000..b8fd584 --- /dev/null +++ b/public/content/blog/digital-mediation-and-the-death-of-nuance-tag.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/content/blog/digital-mediation-and-the-death-of-nuance-vertical.svg b/public/content/blog/digital-mediation-and-the-death-of-nuance-vertical.svg new file mode 100644 index 0000000..ff5f25f --- /dev/null +++ b/public/content/blog/digital-mediation-and-the-death-of-nuance-vertical.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/content/blog/how-chaos-concentrates-control-horizontal.svg b/public/content/blog/how-chaos-concentrates-control-horizontal.svg new file mode 100644 index 0000000..90757e9 --- /dev/null +++ b/public/content/blog/how-chaos-concentrates-control-horizontal.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/content/blog/how-chaos-concentrates-control-tag.svg b/public/content/blog/how-chaos-concentrates-control-tag.svg new file mode 100644 index 0000000..0bf598d --- /dev/null +++ b/public/content/blog/how-chaos-concentrates-control-tag.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/public/content/blog/how-chaos-concentrates-control-vertical.svg b/public/content/blog/how-chaos-concentrates-control-vertical.svg new file mode 100644 index 0000000..7757e91 --- /dev/null +++ b/public/content/blog/how-chaos-concentrates-control-vertical.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/content/blog/integrating-new-members-without-dilution-horizontal.svg b/public/content/blog/integrating-new-members-without-dilution-horizontal.svg new file mode 100644 index 0000000..2f0152f --- /dev/null +++ b/public/content/blog/integrating-new-members-without-dilution-horizontal.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/content/blog/integrating-new-members-without-dilution-tag.svg b/public/content/blog/integrating-new-members-without-dilution-tag.svg new file mode 100644 index 0000000..b8fd584 --- /dev/null +++ b/public/content/blog/integrating-new-members-without-dilution-tag.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/content/blog/integrating-new-members-without-dilution-vertical.svg b/public/content/blog/integrating-new-members-without-dilution-vertical.svg new file mode 100644 index 0000000..3bb6d8e --- /dev/null +++ b/public/content/blog/integrating-new-members-without-dilution-vertical.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/content/blog/knowledge-management-and-institutional-amnesia-horizontal.svg b/public/content/blog/knowledge-management-and-institutional-amnesia-horizontal.svg new file mode 100644 index 0000000..44f1935 --- /dev/null +++ b/public/content/blog/knowledge-management-and-institutional-amnesia-horizontal.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/content/blog/knowledge-management-and-institutional-amnesia-tag.svg b/public/content/blog/knowledge-management-and-institutional-amnesia-tag.svg new file mode 100644 index 0000000..873505c --- /dev/null +++ b/public/content/blog/knowledge-management-and-institutional-amnesia-tag.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/public/content/blog/knowledge-management-and-institutional-amnesia-vertical.svg b/public/content/blog/knowledge-management-and-institutional-amnesia-vertical.svg new file mode 100644 index 0000000..5e7e991 --- /dev/null +++ b/public/content/blog/knowledge-management-and-institutional-amnesia-vertical.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/content/blog/making-decisions-without-hierarchy-horizontal.svg b/public/content/blog/making-decisions-without-hierarchy-horizontal.svg new file mode 100644 index 0000000..eda1836 --- /dev/null +++ b/public/content/blog/making-decisions-without-hierarchy-horizontal.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/content/blog/making-decisions-without-hierarchy-tag.svg b/public/content/blog/making-decisions-without-hierarchy-tag.svg new file mode 100644 index 0000000..0bf598d --- /dev/null +++ b/public/content/blog/making-decisions-without-hierarchy-tag.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/public/content/blog/making-decisions-without-hierarchy-vertical.svg b/public/content/blog/making-decisions-without-hierarchy-vertical.svg new file mode 100644 index 0000000..e477b9c --- /dev/null +++ b/public/content/blog/making-decisions-without-hierarchy-vertical.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/content/blog/operational-security-mutual-aid-tag.svg b/public/content/blog/operational-security-mutual-aid-tag.svg new file mode 100644 index 0000000..873505c --- /dev/null +++ b/public/content/blog/operational-security-mutual-aid-tag.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/public/content/blog/resolving-active-conflicts-tag.svg b/public/content/blog/resolving-active-conflicts-tag.svg new file mode 100644 index 0000000..b8fd584 --- /dev/null +++ b/public/content/blog/resolving-active-conflicts-tag.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/stories/content/ContentThumbnailTemplate.stories.js b/stories/content/ContentThumbnailTemplate.stories.js index 8f56b27..db74ab2 100644 --- a/stories/content/ContentThumbnailTemplate.stories.js +++ b/stories/content/ContentThumbnailTemplate.stories.js @@ -1,13 +1,17 @@ import ContentThumbnailTemplate from "../../app/components/content/ContentThumbnailTemplate"; const mockPost = { - slug: "sample-article", + slug: "resolving-active-conflicts", frontmatter: { - title: "Sample Article Title", + title: "Resolving Active Conflicts", description: - "This is a sample article description that explains what the article covers.", - author: "Sample Author", - date: "2025-01-15", + "Practical steps for resolving conflicts while maintaining trust, cooperation, and shared goals", + author: "Author name", + date: "2025-04-15", + thumbnail: { + vertical: "resolving-active-conflicts-vertical.svg", + horizontal: "resolving-active-conflicts-horizontal.svg", + }, }, }; diff --git a/tests/pages/learn.test.tsx b/tests/pages/learn.test.tsx new file mode 100644 index 0000000..560a66d --- /dev/null +++ b/tests/pages/learn.test.tsx @@ -0,0 +1,113 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { screen, within } from "@testing-library/react"; +import { renderWithProviders as render } from "../utils/test-utils"; +import LearnPage from "../../app/(marketing)/learn/page"; + +vi.mock("../../lib/content", () => ({ + getAllBlogPosts: vi.fn(), +})); + +vi.mock("../../app/components/sections/AskOrganizer", () => ({ + default: ({ + title, + subtitle, + buttonText, + }: { + title: string; + subtitle: string; + buttonText: string; + }) => ( +
+

{title}

+

{subtitle}

+ +
+ ), +})); + +const mockPosts = [ + { + slug: "resolving-active-conflicts", + frontmatter: { + title: "Resolving Active Conflicts", + description: "Practical steps for resolving conflicts", + author: "Author name", + date: "2025-04-15", + thumbnail: { + vertical: "resolving-active-conflicts-vertical.svg", + horizontal: "resolving-active-conflicts-horizontal.svg", + }, + }, + content: "", + htmlContent: "", + filePath: "resolving-active-conflicts.md", + lastModified: new Date(), + }, + { + slug: "operational-security-mutual-aid", + frontmatter: { + title: "Operational Security for Mutual Aid", + description: "Tactics to protect members", + author: "Author name", + date: "2025-04-10", + thumbnail: { + vertical: "operational-security-mutual-aid-vertical.svg", + horizontal: "operational-security-mutual-aid-horizontal.svg", + }, + }, + content: "", + htmlContent: "", + filePath: "operational-security-mutual-aid.md", + lastModified: new Date(), + }, +]; + +describe("LearnPage", () => { + beforeEach(async () => { + vi.clearAllMocks(); + const { getAllBlogPosts } = await import("../../lib/content"); + vi.mocked(getAllBlogPosts).mockReturnValue(mockPosts); + }); + + it("renders content lockup and ask organizer copy", () => { + render(); + + expect(screen.getByText("Organizing is hard")).toBeInTheDocument(); + expect( + screen.getByText( + /Find answers to your questions and see how other groups/, + ), + ).toBeInTheDocument(); + expect(screen.getByTestId("ask-organizer")).toBeInTheDocument(); + expect(screen.getByText("Still have questions?")).toBeInTheDocument(); + }); + + it("renders one card per post in each layout region without duplication", () => { + const { container } = render(); + + const mobileRegion = container.querySelector(".smd\\:hidden"); + const desktopRegion = container.querySelector(".smd\\:grid"); + + expect(mobileRegion).toBeTruthy(); + expect(desktopRegion).toBeTruthy(); + + const mobileLinks = within(mobileRegion as HTMLElement).getAllByRole( + "link", + ); + const desktopLinks = within(desktopRegion as HTMLElement).getAllByRole( + "link", + ); + + expect(mobileLinks).toHaveLength(mockPosts.length); + expect(desktopLinks).toHaveLength(mockPosts.length); + + expect(mobileLinks[0]).toHaveAttribute( + "href", + "/blog/resolving-active-conflicts", + ); + expect(desktopLinks[1]).toHaveAttribute( + "href", + "/blog/operational-security-mutual-aid", + ); + }); +}); diff --git a/tests/unit/ContentContainer.test.jsx b/tests/unit/ContentContainer.test.jsx index 3e18cf3..5fe4aae 100644 --- a/tests/unit/ContentContainer.test.jsx +++ b/tests/unit/ContentContainer.test.jsx @@ -5,6 +5,12 @@ import ContentContainer from "../../app/components/content/ContentContainer"; // Mock asset utils vi.mock("../../lib/assetUtils", () => ({ getAssetPath: vi.fn((asset) => `/assets/${asset}`), + contentBlogTagPath: vi.fn((slug) => `/content/blog/${slug}-tag.svg`), + CONTENT_CATALOG_SLUG_ORDER: [ + "resolving-active-conflicts", + "operational-security-mutual-aid", + "making-decisions-without-hierarchy", + ], ASSETS: { ICON_1: "Icon_1.svg", ICON_2: "Icon_2.svg", @@ -121,7 +127,8 @@ describe("ContentContainer", () => { const metadataContainer = screen.getByText("Test Author").closest("div"); expect(metadataContainer).toHaveClass( "flex", - "items-center", + "min-w-0", + "items-end", "gap-[var(--measures-spacing-008)]", ); }); @@ -148,26 +155,29 @@ describe("ContentContainer", () => { ); }); - it("cycles through different icons based on slug", () => { + it("uses per-article tag assets for catalog slugs", () => { const { rerender } = render(); - // First render should use Icon_1 let icon = screen.getByAltText("Icon for Test Article Title"); expect(icon).toHaveAttribute("src", "/assets/Icon_1.svg"); - // Test with different slug const post2 = { ...mockPost, slug: "operational-security-mutual-aid" }; rerender(); icon = screen.getByAltText("Icon for Test Article Title"); - expect(icon).toHaveAttribute("src", "/assets/Icon_2.svg"); + expect(icon).toHaveAttribute( + "src", + "/content/blog/operational-security-mutual-aid-tag.svg", + ); - // Test with another slug const post3 = { ...mockPost, slug: "making-decisions-without-hierarchy" }; rerender(); icon = screen.getByAltText("Icon for Test Article Title"); - expect(icon).toHaveAttribute("src", "/assets/Icon_3.svg"); + expect(icon).toHaveAttribute( + "src", + "/content/blog/making-decisions-without-hierarchy-tag.svg", + ); }); it("handles missing post data gracefully", () => { @@ -191,7 +201,7 @@ describe("ContentContainer", () => { expect(icon).toHaveClass("w-[60px]", "h-[30px]"); const title = screen.getByText("Test Article Title"); - expect(title).toHaveClass("text-[18px]", "leading-[120%]"); + expect(title).toHaveClass("text-[18px]", "leading-[22px]"); const description = screen.getByText(/This is a test article description/); expect(description).toHaveClass("text-[12px]", "leading-[16px]"); diff --git a/tests/unit/ContentThumbnailTemplate.test.jsx b/tests/unit/ContentThumbnailTemplate.test.jsx index 8bdc018..48636bc 100644 --- a/tests/unit/ContentThumbnailTemplate.test.jsx +++ b/tests/unit/ContentThumbnailTemplate.test.jsx @@ -2,7 +2,6 @@ import { describe, it, expect, vi } from "vitest"; import { render, screen } from "@testing-library/react"; import ContentThumbnailTemplate from "../../app/components/content/ContentThumbnailTemplate"; -// Mock Next.js components vi.mock("next/link", () => { return { default: ({ children, href, ...props }) => ( @@ -13,13 +12,6 @@ vi.mock("next/link", () => { }; }); -vi.mock("next/image", () => { - return { - default: ({ src, alt, ...props }) => {alt}, - }; -}); - -// Mock blog post data const mockPost = { slug: "test-post", frontmatter: { @@ -35,18 +27,14 @@ const mockPost = { }, }; -// Pure presentational; no provider context needed. describe("ContentThumbnailTemplate", () => { describe("Vertical Variant", () => { - it("should render vertical variant with responsive dimensions", () => { + it("should render vertical variant with fluid Figma aspect ratio", () => { render(); const container = screen.getByRole("link"); - expect(container).toBeInTheDocument(); - - // Check that the component has the correct classes for responsive dimensions - const thumbnailDiv = container.querySelector("div"); - expect(thumbnailDiv).toHaveClass("w-full", "aspect-[2/3]"); + const thumbnailDiv = container.querySelector("div.relative"); + expect(thumbnailDiv).toHaveClass("aspect-[260/390]", "w-full"); }); it("should display post title and description", () => { @@ -67,29 +55,20 @@ describe("ContentThumbnailTemplate", () => { }); describe("Horizontal Variant", () => { - it("should render horizontal variant with responsive sizing", () => { + it("should render horizontal variant with fluid Figma aspect ratio", () => { render(); const container = screen.getByRole("link"); - expect(container).toBeInTheDocument(); - - // Check that the component has the correct classes for horizontal layout - const thumbnailDiv = container.querySelector("div"); - expect(thumbnailDiv).toHaveClass( - "min-w-[320px]", - "max-w-[800px]", - "h-[225.5px]", - ); + const thumbnailDiv = container.querySelector("div.relative"); + expect(thumbnailDiv).toHaveClass("aspect-[320/225.5]", "w-full"); }); - it("should display post information in horizontal layout", () => { - render(); + it("should render fixed vertical dimensions when sizing is fixed", () => { + render(); - expect(screen.getByText("Test Blog Post Title")).toBeInTheDocument(); - expect( - screen.getByText(/This is a test description/), - ).toBeInTheDocument(); - expect(screen.getByText("Test Author")).toBeInTheDocument(); + const container = screen.getByRole("link"); + const thumbnailDiv = container.querySelector("div.relative"); + expect(thumbnailDiv).toHaveClass("h-[390px]", "w-[260px]"); }); }); @@ -99,83 +78,32 @@ describe("ContentThumbnailTemplate", () => { , ); - const container = screen.getByRole("link"); - expect(container).toHaveClass("custom-class"); + expect(screen.getByRole("link")).toHaveClass("custom-class"); }); it("should generate correct link href", () => { render(); - const link = screen.getByRole("link"); - expect(link).toHaveAttribute("href", "/blog/test-post"); - }); - - it("should handle posts without tags gracefully", () => { - const postWithoutTags = { - ...mockPost, - frontmatter: { - ...mockPost.frontmatter, - tags: [], - }, - }; - - render(); - - // Should still render without errors - expect(screen.getByText("Test Blog Post Title")).toBeInTheDocument(); - }); - - it("should handle posts without thumbnail images", () => { - const postWithoutImages = { - ...mockPost, - frontmatter: { - ...mockPost.frontmatter, - thumbnail: undefined, - }, - }; - - render(); - - // Should still render without errors - expect(screen.getByText("Test Blog Post Title")).toBeInTheDocument(); + expect(screen.getByRole("link")).toHaveAttribute( + "href", + "/blog/test-post", + ); }); it("should use article-specific thumbnail images when provided", () => { render(); - // Check that the background image uses the article-specific thumbnail - const backgroundImg = document.querySelector( - "img[alt*='Background for']", - ); + const backgroundImg = document.querySelector('img[src*="test-post-vertical"]'); expect(backgroundImg).toBeInTheDocument(); - expect(backgroundImg.src).toContain("test-post-vertical.svg"); }); - it("should use article-specific horizontal images for horizontal variant", () => { + it("should use horizontal thumbnail for horizontal variant", () => { render(); - // Check that the background image uses the article-specific horizontal thumbnail const backgroundImg = document.querySelector( - "img[alt*='Background for']", + 'img[src*="test-post-horizontal"]', ); expect(backgroundImg).toBeInTheDocument(); - expect(backgroundImg.src).toContain("test-post-horizontal.svg"); - }); - }); - - describe("Default Behavior", () => { - it("should default to vertical variant when no variant specified", () => { - render(); - - const thumbnailDiv = screen.getByRole("link").querySelector("div"); - expect(thumbnailDiv).toHaveClass("w-full", "aspect-[2/3]"); - }); - - it("should show metadata by default", () => { - render(); - - expect(screen.getByText("Test Author")).toBeInTheDocument(); - expect(screen.getByText("April 2025")).toBeInTheDocument(); }); }); });