Files
community-rule/docs/template-recommendation-matrix.md
T
2026-04-17 23:45:29 -06:00

33 KiB
Raw Blame History

Template Recommendation Matrix — Implementation Context (CR-88)

Status: Draft / context doc. Reference only — not yet implemented. Linear: CR-88 Roadmap: docs/backend-roadmap.md §4 (RuleTemplate) and §13. Spec ticket: docs/backend-linear-tickets.md Ticket 16.

This doc consolidates the four product-authored matrix spreadsheets with the existing data model, create-flow facets, and section structure so we have a single reference while implementing the importer + recommendation API.

Scope note: No data, API, or UI surface for this feature is in production yet. Backwards compatibility is not a constraint — we will replace the hand-typed prisma/seed.ts COMPOSITION_BY_SLUG map, the existing GET /api/templates response shape, and the static messages/en/create/*.json card decks where it makes the design cleaner.


1. Goal (one paragraph)

Replace the hand-curated prisma/seed.ts COMPOSITION_BY_SLUG map with a spreadsheet-authored matrix for each rule section (Communication, Membership, Decision-making, Conflict management — and later Values), where each row is a method/pattern card and each column is either long-form copy that populates the card UI or a facet flag (✓/x or score) that the recommendation engine uses to filter and rank cards based on the user's create-flow answers (community size, organization type, location/scale, maturity).

The same authoring contract should make it trivial for product to ship updated spreadsheets and have the create-flow card decks (and the home/templates page recommendations) update without any code changes.


2. The four spreadsheets

All four xlsx files share the same column shape: leading content columns + trailing facet columns (✓ / x cells). Sheet name is Current in every workbook.

2.1 Shared facet columns (last 19, identical across the four sheets)

Order is preserved here because the columns are positional in the sheets:

# Column header (xlsx) Maps to wizard step / state field Wizard chip label (messages/en/create/...)
1 1 member community-sizeselectedCommunitySizeIds (id "1") 1 member
2 2-5 members id "2" 2-5 members
3 6-12 members id "3" 6-12 members
4 13-100 members id "4" 13-100 members
5 100-100,000 members id "5" 100-100,000 members
6 Organization Type:DAO (or DAO in conflict/comms/membership) community-structureselectedOrganizationTypeIds (id "6" in organizationTypes) DAO
7 Organization Type:For profit business (or For profit business) id "5" in organizationTypes For profit business
8 Organization Type:Nonprofit (or Nonprofit) id "4" in organizationTypes Nonprofit
9 Organization Type:Open source project (or Open source project) id "3" Open source project
10 Organization Type:Mutual aid (or Mutual aid) id "2" Mutual aid
11 Organization Type: Workers coop (or Workers coop) id "1" Workers coop
12 Location: Global (or Global) community-structureselectedScaleIds (id "4" in scaleOptions) Global
13 Location: National (or National) id "3" National
14 Location: Regional (or Regional) id "2" Regional
15 Location: Local (or Local) id "1" Local
16 Organizational Maturity: Early stage (or Early stage) community-structureselectedMaturityIds (id "1" in maturityOptions) Early stage
17 Organizational Maturity: Growth stage (or Growth stage) id "2" Growth stage
18 Organizational Maturity: Established (or Established) id "3" Established
19 Organizational Maturity: Enterprise (or Enterprise) id "4" Enterprise

Important normalization rules (importer must enforce):

  • Decision-making prefixes columns with Organization Type:, Location:, Organizational Maturity:. The other three sheets drop the prefix. Importer should normalize to a single canonical key (e.g. orgType.workersCoop, scale.local, maturity.earlyStage, size.6_12).
  • Cell value semantics: → match, x (lowercase) → no match, blank → no match, numbers → optional weighted score (only Decision-making.xlsx row 32 contains a non-symbol cell — "Military, Corporations" in the size column — see §2.4 data-quality issues).
  • Wizard chip ids are positional 1..N within each messages/en/create/* array (see chipRowsFromLabels in app/create/screens/select/CommunityStructureSelectScreen.tsx lines 4957). The importer should emit a stable lookup table mapping (facetGroup, label) → wizardChipId so the recommendation engine can match a user's selectedXxxIds against the matrix without depending on label spelling.
  • Curly apostrophes appear in Workers coop. Compare on a normalized key, not on raw label.

2.2 Communication Methods (Communication Methods.xlsx, sheet Current)

Maps 1:1 to messages/en/create/communication.json and the communication-methods step (app/create/screens/card/CommunicationMethodsScreen.tsx).

Content columns (positions 15):

Sheet column Card field
Label cards[<id>].label and modals[<id>].title
Description cards[<id>].supportText and modals[<id>].description
Core Principle & Scope modals[<id>].sections.corePrinciple
Logistics, Admin & Norms modals[<id>].sections.logisticsAdmin
Code of Conduct modals[<id>].sections.codeOfConduct

SECTION_FIELDS = ["corePrinciple", "logisticsAdmin", "codeOfConduct"] is the source of truth (CommunicationMethodsScreen.tsx).

Card rows (11): In-Person Meetings · Signal · Video Meetings · Loomio · Matrix / Element · GitHub / GitLab · Discord · Email Distribution List · Slack · WhatsApp · Discourse (Forum).

2.3 Membership / Group-Membership (Group_Membership_Methods.xlsx, sheet Current)

Maps to the membership-methods step (app/create/screens/card/MembershipMethodsScreen.tsx) and messages/en/create/membership.json.

Content columns (positions 15):

Sheet column Card field (proposed naming)
Label cards[<id>].label / modal title
Description cards[<id>].supportText / modal description
Eligibility & Philosophy modal section A (eligibilityPhilosophy)
Joining Process modal section B (joiningProcess)
Expectations & Removal modal section C (expectationsRemoval)

Card rows (19): Open Access · Orientation Required · Invitation Only · Contribution Based · Mentorship · Peer Sponsorship · Consensus or Vote-Based Approval · Trial Period / Provisional Membership · Referral System with Screening · Membership Agreement or Pledge · Weighted or Tiered Membership · Hybrid Approval Process · Skill-Based Contribution · Pay-to-Join · Application & Review · Identity Verification · Collective Interviews · Skill-Based Evaluation · Lottery / Sortition.

The wizard's existing membership.json modal section keys do not yet match these. Since backwards compatibility is not a constraint, rename the wizard's section keys to match the matrix (eligibilityPhilosophy / joiningProcess / expectationsRemoval) when wiring this up — the existing copy is placeholder.

2.4 Decision-making (Decision-making.xlsx, sheet Current)

Maps to the decision-approaches step (app/create/screens/right-rail/DecisionApproachesScreen.tsx) and messages/en/create/rightRail.json.

Content columns (positions 17):

Sheet column Card field (proposed naming)
Label card title
Description card support text
Core Principle modal section A (corePrinciple)
Applicable Scope modal section B (applicableScope) — free-text examples, e.g. "Daily Operations, Minor Expenditures"
Consensus Level numeric 0.01.0 stored under scalars.consensusLevel (e.g. 0.51, 0.67, 1.0) — drives the Consensus axis in any future visual sort/filter
Step-by-Step Instructions modal section C (stepByStep)
Objections & Deadlocks modal section D (objectionsDeadlocks)

Card rows (32): Lazy Consensus · Do-ocracy · Consensus Decision-Making · Rotational Leadership · Modified Consensus · Consensus Seeking with Delegates · Sociocracy · Supermajority Rule · Ranked Choice Voting · Range Voting · Majority Rule · Approval Voting · Weighted Voting · Cumulative Voting · Quadratic Voting · Continuous Voting · Holacracy · Collaborative Platforms · Deliberative Polling · Investor-Filled Board Seats · Elected Board of Directors · Advisory Committees · Delegated Decision-Making · Executive Committees · First Past the Post · Lottery/Sortition · Proof of Work · Random Choice · Algorithm-Driven Decisions · Autocratic Decision-Making · Hierarchical Decision-Making · Negotiated Decisions.

Data-quality issues to handle in the importer (do not silently drop):

  • Row 32 (Hierarchical Decision-Making): the Consensus Level cell contains "Military, Corporations" (the value clearly belongs to Applicable Scope, which itself already contains "Military, Corporations"). Importer should flag this as a validation error and require a fix in the source workbook rather than try to repair it.
  • Row 11 (Range Voting): the last facet column (Maturity: Enterprise) is empty in the source — treat empty as x (no match) only after the importer logs a warning so the author knows it wasn't intentional ✓.

2.5 Conflict Management (Conflict Management Methods.xlsx, sheet Current)

Maps to the conflict-management step (app/create/screens/card/ConflictManagementScreen.tsx) and messages/en/create/conflictManagement.json.

Content columns (positions 16):

Sheet column Card field (proposed naming)
Title card title (note: not Label like the other three)
Description card support text
Core Principle modal section A (corePrinciple)
Applicable Scope modal section B (applicableScope)
Process Protocol modal section C (processProtocol)
Restoration & Fallbacks modal section D (restorationFallbacks)

Card rows (19): Peer Mediation · Conflict Resolution Council · Facilitated Negotiation · Ad Hoc Arbitration · Conflict Workshops · Supermajority Vote · Interest-Based Bargaining · Restorative Practices · Mediation · Circle Processes · Judicial Committees · Managerial Decision · Internal Tribunal · Consensus Building · Binding Arbitration · Non-Binding Arbitration · Binding Contracts · Lottery/Sortition · Rotational Judging.

Conflict Management sheet uses Title instead of Label and omits the Organization Type: / Location: / Organizational Maturity: prefixes — normalize both at import time.


3. Existing data model & wizard surface area

3.1 RuleTemplate (today)

model RuleTemplate {
  id          String   @id @default(cuid())
  slug        String   @unique
  title       String
  category    String?
  description String?
  body        Json
  sortOrder   Int      @default(0)
  featured    Boolean  @default(false)
}

body JSON is the rendered rule document ({ sections: [{ categoryName, entries: [{ title, body }] }, ...] }), authored today by the bodyFromXlsxComposition() helper in prisma/seed.ts from a hand-typed COMPOSITION_BY_SLUG map.

Section ordering (canonical): Values → Communication → Membership → Decision-making → Conflict management. Final-review and governancePatternBody both rely on this exact order and casing.

function governancePatternBody(coreValues: string): Prisma.InputJsonValue {
  return {
    sections: [
      { categoryName: "Values", entries: [{ title: "Core stance", body: coreValues }] },
      { categoryName: "Communication", entries: [...] },
      { categoryName: "Membership", entries: [...] },
      { categoryName: "Decision-making", entries: [...] },
      { categoryName: "Conflict management", entries: [...] },
    ],
  };
}

3.2 Wizard facets captured today (CreateFlowState)

selectedCommunitySizeIds?: string[];
selectedOrganizationTypeIds?: string[];
selectedScaleIds?: string[];
selectedMaturityIds?: string[];
selectedCoreValueIds?: string[];
selectedCommunicationMethodIds?: string[];
selectedMembershipMethodIds?: string[];
selectedDecisionApproachIds?: string[];
selectedConflictManagementIds?: string[];

The first four are exactly the four facet groups in the matrix sheets. The last four are the user's chosen cards per section, which the recommendation flow can either pre-select (when picked from a template) or feed back into ranking.

These same fields are validated server-side by createFlowStateSchema in lib/server/validation/createFlowSchemas.ts (lines 47106) — the recommend endpoint should reuse that schema (or a strict subset) instead of redefining the facet shape.

3.3 Wizard step order

Source of truth is app/create/utils/flowSteps.ts (FLOW_STEP_ORDER). The relevant slice is:

review → core-values → communication-methods → membership-methods →
decision-approaches → conflict-management → confirm-stakeholders → final-review

docs/create-flow.md's step table is stale; trust flowSteps.ts.

3.4 Where templates already surface in the UI

Surface File
Marketing home "Popular templates" app/(marketing)/MarketingRuleStackSection.tsx
Templates index app/(marketing)/templates/page.tsx
Template preview (by slug) app/create/review-template/[slug]/page.tsx
"Use without changes" → publish app/create/CreateFlowLayoutClient.tsx handleUseTemplateWithoutChanges
API list app/api/templates/route.ts (GET only, no params)

There is currently no recommendation logic, no facet filtering, and the /create/informational?template=<slug> query param is a known no-op (see CreateFlowLayoutClient.tsx lines 479482).


4. Repo conventions to follow (don't reinvent)

These are the patterns the implementation must match. References point at the canonical example for each.

4.1 API routes (app/api/**/route.ts)

app/api/drafts/me/route.ts is the reference — every new route in this feature must match this exact shape:

  1. if (!isDatabaseConfigured()) return dbUnavailable(); — always first. (lib/server/env.ts, lib/server/responses.ts).
  2. For auth'd routes: const user = await getSessionUser(); then return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); if missing. (Recommendation read endpoints can stay unauthenticated.)
  3. For request bodies: readLimitedJson(request)<schema>.safeParse(parsed.value)jsonFromZodError(validated.error) on failure. (lib/server/validation/requestBody.ts, lib/server/validation/zodHttp.ts).
  4. Success: NextResponse.json({ <key>: data }) — flat object with one or two named keys, no success: true envelope.
  5. Errors: structured { error: { code, message } } (Zod path) or simple { error: "..." } (auth path). Match what's already in the repo.
  6. Server-side query helpers swallow Prisma failures and return []/null (see listRuleTemplatesFromDb in lib/server/ruleTemplates.ts lines 930). Routes do not wrap helper calls in try/catch.

4.2 Zod schemas live in lib/server/validation/

  • One file per feature area (e.g. createFlowSchemas.ts, future templateRecommendationSchemas.ts).
  • Export the schema and the inferred type (export type X = z.infer<typeof xSchema>).
  • Wrap any free-form JSON blobs with assertPlainJsonValue / DEFAULT_PLAIN_JSON_LIMITS (lib/server/validation/plainJson.ts) so the size/depth bounds match the rest of the API.
  • Reuse FLOW_STEP_ORDER and existing array bounds where they overlap (see the selectedXxxMethodIds arrays in createFlowStateSchema).

4.3 Prisma access

  • Singleton: import { prisma } from "lib/server/db"; — never new PrismaClient() from app code. (Standalone scripts under scripts/ / prisma/ may instantiate their own, matching prisma/seed.ts lines 363403.)
  • Server-only "fetch/list" helpers live under lib/server/<feature>.ts, return DTOs (not raw Prisma rows), and degrade gracefully (isDatabaseConfigured() short-circuit, try/catch → empty result).
  • No $transaction patterns exist yet; introduce one for the importer (write TemplateMethod + TemplateMethodFacet rows atomically).

4.4 DTO style

  • Hand-written type aliases that mirror a Prisma select clause, co-located with the consumer (see RuleTemplateDto in lib/create/fetchTemplates.ts lines 514).
  • For a feature with both client and server consumers, put the type in lib/<feature>/types.ts and import from both sides.

4.5 Standalone scripts

  • Use tsx (already a dev dep; entry point package.json prisma.seed field).
  • Layout matches prisma/seed.ts: async function main(), log a one-line success summary, console.error(e); process.exit(1) on failure, await prisma.$disconnect() in finally.
  • Add an entry to package.json scripts (e.g. "templates:import": "tsx scripts/import-templates-xlsx.ts").
  • No shared dotenv loader — rely on env from the shell / Next runtime.
  • Support a --dry-run flag that validates + diffs without writing.

4.6 Tests

  • Vitest under tests/unit/*.test.ts for parsers / validators / pure functions (see tests/unit/createFlowValidation.test.ts).
  • API routes are not unit-tested today; cover route behavior indirectly with a tests/unit/templateRecommendationSchemas.test.ts (Zod) plus a fixture workbook + importer test under tests/unit/importTemplatesXlsx.test.ts.
  • E2E for the wizard (if needed) goes under tests/e2e/*.spec.ts — not required for CR-88 acceptance.
  • Test utilities: tests/utils/test-utils.tsx (renderWithProviders); MSW server in tests/msw/server.ts. No Prisma mock helper exists; importer test should use a fixture workbook and stub the prisma client at the import site.

4.7 Logging

  • Use logger from lib/logger.ts for any server-side info/warn/error in scripts and route helpers (matches app/api/auth/magic-link/request/route.ts lines 1415, 3545). No apiError helper exists; do not introduce one.

4.8 New deps

  • xlsx (SheetJS) is not currently in package.json. Add it as a prod dep only if the importer is invoked from app code; if the importer is script-only, devDependency is fine. CR-88's plan calls for a build/CLI-time importer, so devDependencies is the right home.

4.9 i18n / messages/ constraint

  • Card decks and modal copy are currently keyed in messages/en/<feature>.json and read via useMessages().create.<feature> (app/contexts/MessagesContext.tsx, messages/en/index.ts).
  • Only en is wired today, so we don't have a translation backlog blocking us. The wiring step (§7) replaces messages/en/create/{communication, membership,rightRail,conflictManagement}.json card/modal payloads with values served by GET /api/template-methods (still keyed by the same message namespace shape so future i18n can layer on if needed). Header strings, button labels, and other purely-static UI copy stay in messages/en/*.

4.10 .cursorrules scope

  • The repo's .cursorrules PascalCase / lowercase normalization rule applies to React component props only. It does not apply to API query params, request bodies, or DB columns. The recommendation API uses lowercase facet keys throughout (orgType, scale, maturity, size).

5. Authoring contract (informs §6 storage + §7 importer)

The four spreadsheets together imply this row schema (per matrix workbook):

type MatrixRow = {
  /** Stable slug derived from `Label`/`Title` (kebab-case, lowercase, ascii).
   *  Used as the card id everywhere downstream. */
  id: string;

  /** Section this row belongs to. One of: communication, membership,
   *  decisionMaking, conflictManagement. (values is not yet sheet-driven.) */
  section: "communication" | "membership" | "decisionMaking" | "conflictManagement";

  /** Card-facing copy. Keys differ per section; importer normalizes. */
  card: {
    label: string;
    description: string;
    /** Section-specific long-form fields (34 per section). */
    modalSections: Record<string, string>;
  };

  /** Optional numeric scalar fields (e.g. decisionMaking `Consensus Level`). */
  scalars?: Record<string, number>;

  /** Facet matches (✓ → true, x/blank → false). Keys are canonical facet ids. */
  facets: {
    size: Record<"1" | "2_5" | "6_12" | "13_100" | "100_100k", boolean>;
    orgType: Record<"dao" | "forProfit" | "nonprofit" | "openSource" | "mutualAid" | "workersCoop", boolean>;
    scale: Record<"global" | "national" | "regional" | "local", boolean>;
    maturity: Record<"earlyStage" | "growthStage" | "established" | "enterprise", boolean>;
  };
};

A sibling manifest documents the per-section section-key mapping and column header → canonical facet/scalar key mapping, so the importer can be stable across header rewording.


6. Storage (decided: normalized tables)

We are introducing two new Prisma models. Hand-typed COMPOSITION_BY_SLUG in prisma/seed.ts is replaced by template rows that reference method slugs.

model TemplateMethod {
  id            String   @id @default(cuid())
  section       String   // communication | membership | decisionMaking | conflictManagement
  slug          String
  label         String
  description   String
  modalSections Json     // { corePrinciple: "...", logisticsAdmin: "...", ... }
  scalars       Json?    // { consensusLevel: 0.51 }
  sortOrder     Int      @default(0)
  facets        TemplateMethodFacet[]
  @@unique([section, slug])
  @@index([section])
}

model TemplateMethodFacet {
  id        String  @id @default(cuid())
  methodId  String
  group     String  // size | orgType | scale | maturity
  value     String  // e.g. "workersCoop"
  matches   Boolean // ✓ → true, x/blank → false
  weight    Float?  // optional numeric override for future scoring
  method    TemplateMethod @relation(fields: [methodId], references: [id], onDelete: Cascade)
  @@unique([methodId, group, value])
  @@index([group, value, matches])
}

RuleTemplate.body continues to express a chosen composition of methods (one or more per section). Curated templates in prisma/seed.ts become references to TemplateMethod.slug instead of literal copy strings — when copy changes in the spreadsheet, every template that references that slug inherits the new copy.

A follow-up (out of scope for CR-88) may add a RuleTemplateMethodLink join table if templates need ordering or per-template overrides; the current body JSON shape is sufficient for the first ship.


7. Importer (scripts/import-templates-xlsx.ts)

Phased plan that the implementation agent can follow top-to-bottom. Mirrors the structure of prisma/seed.ts (singleton client, main() + finally { $disconnect }, process.exit(1) on failure).

  1. Read .xlsx with xlsx (SheetJS, add as devDependency) from a configurable input dir (default data/template-matrix/). The four workbooks live there as committed artifacts, not in Downloads/.
  2. Schema-validate per section with Zod schemas that live in lib/server/validation/templateRecommendationSchemas.ts so the API and importer share the row shape: required column headers, allowed cell symbols (, x, blank, decimal for Consensus Level).
  3. Normalize: kebab-case slug from label, strip Organization Type: / Location: / Organizational Maturity: prefixes, collapse whitespace, normalize curly quotes.
  4. Cross-sheet validation: facet columns must match the canonical 19-column set; unknown columns fail loudly via the importer (use logger.error).
  5. Diff & upsert inside prisma.$transaction([...]): upsert TemplateMethod rows by (section, slug); delete + recreate TemplateMethodFacet rows for each method.
  6. Emit a JSON snapshot to prisma/data/template-matrix.json so prisma/seed.ts can replay imports when the source workbooks aren't available (e.g. CI seed without the spreadsheet checked in).
  7. Flags: --dry-run (validate + diff, no writes), --allow-warnings (don't fail on the row-32 / row-11 issues in §2.4 while authors are iterating).
  8. Tests in tests/unit/importTemplatesXlsx.test.ts: a fixture workbook with two rows per section asserts both validation errors (unknown column, bad symbol, miscategorized cell) and successful normalization. Reuse Vitest patterns from tests/unit/createFlowValidation.test.ts.

Per Ticket 16 and the roadmap, prefer batch .xlsx import over a live Google Sheets API in production. Authors export to .xlsx and a maintainer runs npm run templates:import (or CI does on a data/template-matrix/ change).


8. APIs

Two read endpoints. Both follow §4.1 conventions exactly: dbUnavailable() guard → server helper from lib/server/templateMethods.tsNextResponse.json({ ... }).

8.1 GET /api/templates (rewrite)

Query params (all optional):

  • facet.size=<chipId> (repeatable)
  • facet.orgType=<chipId> (repeatable)
  • facet.scale=<chipId> (repeatable)
  • facet.maturity=<chipId> (repeatable)

Behavior:

  • No params → existing curated ordering (featured, sortOrder, title), no scoring.
  • With facets → score each template by counting matching facets across the methods referenced in its body; return ranked templates plus an optional scores map.

Response:

{
  templates: RuleTemplateDto[],
  scores?: Record<string, { score: number; matchedFacets: string[] }>
}

Param parsing helper lives next to listRuleTemplatesFromDb in lib/server/ruleTemplates.ts (e.g. parseTemplateFacetsFromSearchParams).

8.2 GET /api/template-methods?section=<section>[&facet.*=...]

Powers the four card-deck wizard steps and the section-level recommendation view. Response:

{
  section: "communication" | "membership" | "decisionMaking" | "conflictManagement",
  methods: Array<{
    slug: string;
    label: string;
    description: string;
    modalSections: Record<string, string>;
    scalars?: Record<string, number>;
    /** Per-method facet match against the requested facets (omitted when no facets passed). */
    matches?: { score: number; matchedFacets: string[] };
  }>
}

Server helper: listTemplateMethodsFromDb({ section, facets }) in lib/server/templateMethods.ts. Same swallow-and-return-[] failure mode as listRuleTemplatesFromDb.

8.3 POST /api/templates/recommend (follow-up, optional)

If product wants to send the full CreateFlowState (not just facet ids), the body schema reuses createFlowStateSchema from lib/server/validation/createFlowSchemas.ts. Same scoring engine, just a richer input. Skip until §8.1 + §8.2 ship.

Empty / partial facets: never error. Fall back to today's ordering and return all rows.


9. Wizard wiring (UI follow-on, not strictly part of CR-88)

Once the API exists:

  • communication-methods / membership-methods / decision-approaches / conflict-management screens each call GET /api/template-methods?section=...&facet.*=.... The card label and modal copy come from the API response, not from messages/en/create/<section>.json. Static JSON in those four files is pruned to the page-level strings (header titles, button labels, modal chrome) only.
  • Selecting a template on the marketing home or templates/ page can prefill the create flow's selected*MethodIds from the template's composition (this closes the ?template= no-op gap noted in CreateFlowLayoutClient.tsx).
  • Recommendations should never hide options from the user — ranking only. Authors expect to see "all 32 decision-making patterns" with the ✓-matching ones surfaced first.

10. Open questions for product before coding

  1. Should Values also be sheet-driven? Today it's free-text only and not in any of the four matrices. Roadmap implies eventual parity.
  2. Scoring vs filtering: do we want to hide non-✓ rows when a facet is set, or only rank them? Recommend ranking with a soft cutoff.
  3. Per-template featured composition vs library-wide: should RuleTemplate rows continue to exist as named compositions ("Consensus", "Elected Board", etc.), or become derived from a "this is the best mix for nonprofit + 13100 + early stage" scoring? Doc today assumes the former — templates remain curated.
  4. Authoring source of truth: are the Downloads/*.xlsx files committed to data/template-matrix/ going forward, or do they live in a Drive folder pulled by the importer at build time? Recommend committing.
  5. Data validation strictness: the current Decision-making sheet has a miscategorized cell (row 32, see §2.4). Importer should fail by default, with a --allow-warnings flag for in-progress edits.

11. Test plan (acceptance for CR-88)

  • scripts/import-templates-xlsx.ts runs end-to-end on the four committed workbooks with no errors and produces the expected DB diff (or JSON snapshot).
  • Editing a row in the source workbook and re-running the importer changes the rank order returned by GET /api/templates?facet.orgType=4 (the Nonprofit chip id) without any manual Studio edit.
  • tests/unit/importTemplatesXlsx.test.ts rejects each documented validation failure (unknown column, bad symbol, miscategorized row).
  • tests/unit/templateRecommendationSchemas.test.ts exercises the Zod schemas the importer and API share.
  • Manual smoke on the four wizard card-deck steps: facet-narrowed ordering surfaces matching cards first; facetless GET returns the full curated list.
  • No regression in existing template surfaces (marketing home, templates index, review-template preview).

12. Source files referenced

  • prisma/schema.prismaRuleTemplate model (lines 6473).
  • prisma/seed.ts — current curated composition + xlsx-shaped helpers (lines 1404).
  • app/api/templates/route.ts — existing GET endpoint (to be rewritten).
  • app/api/drafts/me/route.ts — reference route shape (dbUnavailablegetSessionUserreadLimitedJsonsafeParsejsonFromZodError).
  • lib/server/db.ts — Prisma singleton (lines 118).
  • lib/server/responses.tsdbUnavailable() (lines 18).
  • lib/server/ruleTemplates.tslistRuleTemplatesFromDb (lines 930).
  • lib/server/validation/createFlowSchemas.ts — schema to reuse for POST /api/templates/recommend (lines 47106).
  • lib/server/validation/requestBody.tsreadLimitedJson (lines 1348).
  • lib/server/validation/zodHttp.tsjsonFromZodError (lines 417).
  • lib/server/validation/plainJson.tsassertPlainJsonValue / DEFAULT_PLAIN_JSON_LIMITS.
  • lib/logger.ts — server-side logger.
  • app/create/types.tsCreateFlowState and facet fields.
  • app/create/utils/flowSteps.ts — canonical step order.
  • app/create/utils/createFlowScreenRegistry.ts — screen layout per step.
  • app/create/screens/select/CommunityStructureSelectScreen.tsx — chip-id derivation pattern (positional String(i+1)).
  • app/create/screens/card/CommunicationMethodsScreen.tsx — section-field contract (SECTION_FIELDS).
  • messages/en/create/{communitySize,communityStructure,communication,membership,rightRail,conflictManagement}.json — current static card / chip copy that the matrix supersedes.
  • lib/templates/governanceTemplateCatalog.ts, lib/templates/templateGridPresentation.ts, lib/create/fetchTemplates.ts — current presentation/DTO layer.
  • tests/unit/createFlowValidation.test.ts — Vitest pattern for new schema/importer tests.
  • Roadmap: docs/backend-roadmap.md §4 (lines 8385), §13.
  • Spec: docs/backend-linear-tickets.md Ticket 16 (lines 280304).

13. Source workbooks

File Sheet Rows Cols Section
Communication Methods.xlsx Current 11 cards 24 communication
Group_Membership_Methods.xlsx Current 19 cards 24 membership
Decision-making.xlsx Current 32 cards 26 decisionMaking
Conflict Management Methods.xlsx Current 19 cards 25 conflictManagement

Counts include the header row. Decision-making has 26 columns because of two extra content fields (Consensus Level, Step-by-Step Instructions vs the 4-section pattern of the others).