From cb3eef69049c813c0e371fd15965d32cb03ae613 Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Tue, 28 Apr 2026 21:47:29 +0000 Subject: [PATCH 1/5] docs: think through featured content requirements and layout trade-offs Analyzes the data model gap (repos not yet in snapshot), evaluates layout options for the home page and work index, and proposes an implementation sequence for highlighting Forms, Messaging, Document Extractor, and Forms Lab. --- docs/featured-content.md | 189 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 docs/featured-content.md diff --git a/docs/featured-content.md b/docs/featured-content.md new file mode 100644 index 0000000..012e92d --- /dev/null +++ b/docs/featured-content.md @@ -0,0 +1,189 @@ +# Featured content: design and requirements + +This document thinks through how to present Flexion's featured projects — Forms, Messaging (Notify), Document Extractor, and Forms Lab — within the existing site structure. + +## Goals + +1. **Signal intentionality.** Visitors should immediately understand that these four repos represent Flexion's deliberate, invested product work — not just code that happens to be public. +2. **Stay part of the collection.** Featured projects are *members* of the broader catalog, not a separate silo. They should feel like the "front shelf" of the same library. +3. **Invite exploration.** A visitor drawn in by a featured project should naturally discover the rest of the work index, and vice versa. +4. **Scale gracefully.** The design should work today with 4 featured projects and still work with 6–8 later, without requiring a layout overhaul. + +## Current state + +- The home page has a "Featured labs" section with a 3-column grid, but no repos are marked `featured: true` in `overrides.yml` yet — so it renders empty. +- The work index already sorts featured repos first. +- `document-extractor` exists in `repos.json`. `forms`, `messaging`, and `forms-lab` do **not** yet appear in the GitHub snapshot (they may be private or pending creation). +- Rich content overlays already exist for `forms`, `messaging`, and `document-extractor` in `content/work/`. + +## Data model gap + +The merge logic (`src/catalog/merge.ts`) only produces entries for repos present in `repos.json`. This means: + +- If `forms`, `messaging`, or `forms-lab` don't exist as public repos in the Flexion org, they can't appear on the site at all under the current pipeline. +- Their overlay content is currently orphaned. + +### Options for handling missing repos + +| Approach | Pros | Cons | +|----------|------|------| +| **A. Create the public repos first, let refresh pick them up** | Zero code changes needed. Data model stays simple. Overlays immediately attach. | Requires the repos to actually exist and be public before the site can show them. | +| **B. Add synthetic entries to `repos.json` by hand** | Quick unblock. | Breaks the invariant that `repos.json` is machine-generated and refreshed daily — hand-edits get overwritten. | +| **C. Extend merge logic to create entries from overlays alone** | Allows content-first authoring (write the overlay, mark featured, even before the repo goes public). | Adds complexity. Synthetic entries lack GitHub metadata (stars, license, last push). Need sentinel values or nullable display logic. | +| **D. Add a `data/manual-entries.yml` file for curated repos not yet in the org** | Separates concerns from `repos.json`. Supports a "coming soon" or "private-with-public-face" pattern cleanly. | Another data source to merge. Needs clear lifecycle (remove entry once repo goes public and refresh picks it up). | + +**Recommendation:** Approach **A** is simplest if the repos will be public soon. If we need to ship the featured section before they go public, approach **D** gives the cleanest separation — a small, hand-authored file that explicitly owns synthetic entries and can be retired per-repo as they appear in the refresh pipeline. + +--- + +## Layout and presentation + +### Home page: featured section + +The current `home-featured__grid` (3-column at 48rem+) is appropriate for 3–4 cards. But the standard `RepoCard` — name, one-line summary, tier/category badges — is designed for browsing, not for *selling* a product. Featured projects deserve more presence. + +#### Option 1: Enhanced card (recommended) + +Keep the grid layout but introduce a `FeaturedCard` variant that provides: + +- **Title** (from overlay, e.g. "Forms" not "forms") +- **Tagline/summary** (the overlay's `summary` field, already written to be compelling) +- **2–3 bullet "value props"** — a new optional field in the overlay front-matter, e.g. `highlights: [...]` +- **CTA link** to the detail page + +Why: The featured section lives on the home page, which is a marketing surface. The current RepoCard is neutral/informational; a featured card should persuade. + +#### Option 2: Hero spotlight + carousel + +One project dominates above the fold; others are secondary. More dramatic, but: +- Introduces ordering politics (which one is "first"?) +- Doesn't scale to 6+ without pagination +- Adds JS complexity for carousel behavior + +#### Option 3: Keep current RepoCard, just mark them featured + +Simplest change (just set `featured: true`), but doesn't visually distinguish featured work from the catalog list below. Misses the goal of signaling intentionality. + +**Recommendation:** Option 1 — an enhanced card in the existing grid. It's the minimum change that achieves the "front shelf" feeling without adding interaction complexity. + +### Work index: integration between featured and full catalog + +The work index currently sorts featured repos to the top, which is good. But there's no visual break between them and the rest. Options: + +#### A. Visual separator with section heading (recommended) + +Add a lightweight heading ("Featured" or "Highlighted work") above the featured cluster, with a subtle divider before the rest. Featured repos still live *in* the list (same filter/sort controls apply), but they're visually grouped. + +- Preserves "part of the same collection" feeling +- Featured repos still respond to tier/category filters (if a user filters to "Tool" and a featured repo is a Tool, it stays; if not, it correctly disappears) +- Low implementation cost + +#### B. Separate "pinned" section outside the filter + +Featured repos get a fixed section above the filterable list, unaffected by filters. + +- Guarantees visibility regardless of filter state +- But breaks the "same collection" mental model — they become a separate thing + +#### C. Visual emphasis only (border/background) + +Featured repos get a subtle highlight (accent border, background tint) but stay in normal sort flow with no heading. + +- Lightest touch +- May be too subtle; visitors won't understand *why* some cards look different + +**Recommendation:** Option A — section heading within the list. Featured repos are still part of the filterable collection, but the heading signals "start here." If filtered out by tier/category, the heading hides too, which is correct behavior. + +### Detail pages: connecting back + +Each featured repo already gets a full detail page from the existing `WorkDetail` template. No structural change needed there. But consider: + +- **Cross-linking between featured projects.** If Forms and Forms Lab are related, a "Related projects" section at the bottom of each detail page would help visitors discover the family. This can be driven by a `related: [forms-lab]` field in the overlay front-matter. +- **"Back to featured" breadcrumb vs. "Back to all work."** Since featured repos are *part of* the catalog, the breadcrumb should go to `/work/` (the full index), not to a separate featured page. This reinforces the "same collection" principle. + +--- + +## Content requirements per featured project + +Each featured overlay should communicate: + +| Field | Purpose | Example | +|-------|---------|---------| +| `title` | Human-friendly name | "Forms" | +| `summary` | One-sentence value prop (used on cards) | "Accessible, USWDS-aligned form experiences..." | +| `highlights` (new) | 2–3 bullet differentiators for the featured card | ["WCAG 2.1 AA conformant", "Multi-step with save-and-resume", "Agency-agnostic"] | +| Body `## What it solves` | Problem framing | (already written for 3 of 4) | +| Body `## Who it's for` | Audience | (already written for 3 of 4) | +| Body `## Status` | Maturity signal | (already written for 3 of 4) | +| Body `## Get started` | Entry point for developers | (already written for 3 of 4) | + +**Forms Lab** still needs its overlay (`content/work/forms-lab.md`). + +--- + +## What "Forms Lab" is relative to "Forms" + +This needs clarification for content purposes: + +- If Forms Lab is an **experimental playground** (try components, submit designs), it should be positioned as a companion/sandbox — "where agencies experiment before committing to Forms." +- If it's a **documentation/demo site**, it's more of a resource than a product — maybe not "featured" on its own but linked from the Forms detail page. +- If it's a **separate product** (different use case, different audience), it deserves its own full overlay with distinct positioning. + +The layout accommodates any of these — the question is purely content/positioning. + +--- + +## Navigation and discovery flow + +``` +Home page +├── Featured labs (4 enhanced cards) ──→ /work/{name}/ (detail) +├── "Explore our work" CTA ──────────→ /work/ (full index) +└── Stats section (public projects count, etc.) + +Work index (/work/) +├── [Featured heading] +│ ├── Featured repo card ──────────→ /work/{name}/ +│ ├── Featured repo card ──────────→ /work/{name}/ +│ └── ... +├── [All work heading] +│ ├── Repo card ───────────────────→ /work/{name}/ +│ └── ... +└── Filter controls (tier, category) + +Detail page (/work/{name}/) +├── Full content (overlay body) +├── Stewardship sidebar +├── "View on GitHub" link +└── Related projects (optional, future) +``` + +This means: +- Home → featured detail: 1 click +- Home → full catalog: 1 click +- Featured detail → full catalog: 1 click (breadcrumb / header nav) +- Full catalog → featured detail: 0 extra clicks (they're in the list) + +No dead ends. Featured content is always reachable from the catalog, and the catalog is always reachable from featured content. + +--- + +## Implementation sequence + +1. **Data:** Ensure repos exist in `repos.json` (wait for public repos) or implement approach D (manual entries). +2. **Overrides:** Mark the four repos as `featured: true`, `tier: active`, `category: product` in `overrides.yml`. +3. **Content:** Write `content/work/forms-lab.md`. Add `highlights` field to all four overlays. +4. **Component:** Build `FeaturedCard` component (or extend `RepoCard` with a `variant` prop). +5. **Home page:** Wire `FeaturedCard` into the existing `home-featured__grid`. +6. **Work index:** Add section heading logic for featured cluster. +7. **Stretch:** Add `related` field support and render cross-links on detail pages. + +--- + +## Open questions + +1. Are `forms`, `messaging`, and `forms-lab` going to be public repos in the Flexion org? If not, which data approach (A–D) do we want? +2. What is Forms Lab's relationship to Forms — companion sandbox, docs site, or independent product? +3. Should the featured section on the home page have its own heading/intro copy, or just the grid? +4. Do we want a cap on featured projects (e.g., max 6) to protect the home page layout? +5. For the `highlights` field — should these be auto-derived from the overlay body, or always hand-authored? From d9510095ebb5087e4f4fac616a5f489f08f869ed Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Tue, 28 Apr 2026 22:01:40 +0000 Subject: [PATCH 2/5] docs: update with repo public status and resolve data approach MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit forms-lab is now public, forms is pending, flexion-notify coming soon. Approach A (wait for refresh) confirmed. Note the messaging.md → flexion-notify.md rename needed. --- docs/featured-content.md | 50 +++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/docs/featured-content.md b/docs/featured-content.md index 012e92d..7ca59aa 100644 --- a/docs/featured-content.md +++ b/docs/featured-content.md @@ -13,26 +13,33 @@ This document thinks through how to present Flexion's featured projects — Form - The home page has a "Featured labs" section with a 3-column grid, but no repos are marked `featured: true` in `overrides.yml` yet — so it renders empty. - The work index already sorts featured repos first. -- `document-extractor` exists in `repos.json`. `forms`, `messaging`, and `forms-lab` do **not** yet appear in the GitHub snapshot (they may be private or pending creation). -- Rich content overlays already exist for `forms`, `messaging`, and `document-extractor` in `content/work/`. +- `document-extractor` exists in `repos.json`. The others are in various stages of going public: + - `forms-lab` — now public (will appear in next refresh) + - `forms` — public access requested, pending + - `flexion-notify` — will be made public soon +- Rich content overlays exist for `forms`, `messaging`, and `document-extractor` in `content/work/`. +- **Naming mismatch:** The Notify repo is `flexion-notify` on GitHub, but the overlay is `content/work/messaging.md`. It needs to be renamed to `content/work/flexion-notify.md` for the merge logic to attach it. ## Data model gap -The merge logic (`src/catalog/merge.ts`) only produces entries for repos present in `repos.json`. This means: +The merge logic (`src/catalog/merge.ts`) only produces entries for repos present in `repos.json`. Overlay content for repos not yet in the snapshot is orphaned until they appear. -- If `forms`, `messaging`, or `forms-lab` don't exist as public repos in the Flexion org, they can't appear on the site at all under the current pipeline. -- Their overlay content is currently orphaned. +### Resolution: Approach A (wait for public repos) -### Options for handling missing repos +All four repos are going public: -| Approach | Pros | Cons | -|----------|------|------| -| **A. Create the public repos first, let refresh pick them up** | Zero code changes needed. Data model stays simple. Overlays immediately attach. | Requires the repos to actually exist and be public before the site can show them. | -| **B. Add synthetic entries to `repos.json` by hand** | Quick unblock. | Breaks the invariant that `repos.json` is machine-generated and refreshed daily — hand-edits get overwritten. | -| **C. Extend merge logic to create entries from overlays alone** | Allows content-first authoring (write the overlay, mark featured, even before the repo goes public). | Adds complexity. Synthetic entries lack GitHub metadata (stars, license, last push). Need sentinel values or nullable display logic. | -| **D. Add a `data/manual-entries.yml` file for curated repos not yet in the org** | Separates concerns from `repos.json`. Supports a "coming soon" or "private-with-public-face" pattern cleanly. | Another data source to merge. Needs clear lifecycle (remove entry once repo goes public and refresh picks it up). | +| Repo | Status | Action needed | +|------|--------|---------------| +| `document-extractor` | Already in snapshot | None | +| `forms-lab` | Now public | Next daily refresh (or manual dispatch) picks it up | +| `forms` | Public access requested | Wait for approval, then refresh picks it up | +| `flexion-notify` | Will be made public soon | Wait, then refresh picks it up | -**Recommendation:** Approach **A** is simplest if the repos will be public soon. If we need to ship the featured section before they go public, approach **D** gives the cleanest separation — a small, hand-authored file that explicitly owns synthetic entries and can be retired per-repo as they appear in the refresh pipeline. +No code changes needed for the data pipeline. Once a repo is public, the 09:00 UTC refresh workflow adds it to `repos.json` automatically. + +### Content file rename needed + +The overlay at `content/work/messaging.md` must be renamed to `content/work/flexion-notify.md` to match the actual GitHub repo name. The merge logic joins on filename slug → repo name. --- @@ -170,19 +177,20 @@ No dead ends. Featured content is always reachable from the catalog, and the cat ## Implementation sequence -1. **Data:** Ensure repos exist in `repos.json` (wait for public repos) or implement approach D (manual entries). -2. **Overrides:** Mark the four repos as `featured: true`, `tier: active`, `category: product` in `overrides.yml`. -3. **Content:** Write `content/work/forms-lab.md`. Add `highlights` field to all four overlays. -4. **Component:** Build `FeaturedCard` component (or extend `RepoCard` with a `variant` prop). -5. **Home page:** Wire `FeaturedCard` into the existing `home-featured__grid`. -6. **Work index:** Add section heading logic for featured cluster. -7. **Stretch:** Add `related` field support and render cross-links on detail pages. +1. **Rename:** `content/work/messaging.md` → `content/work/flexion-notify.md` (update title/summary to reflect repo name if needed). +2. **Content:** Write `content/work/forms-lab.md`. Add `highlights` field to all four overlays. +3. **Overrides:** Mark all four repos as `featured: true`, `tier: active`, `category: product` in `overrides.yml`. +4. **Refresh:** Trigger manual catalog refresh (or wait for daily run) once repos are public, to populate `repos.json`. +5. **Component:** Build `FeaturedCard` component (or extend `RepoCard` with a `variant` prop). +6. **Home page:** Wire `FeaturedCard` into the existing `home-featured__grid`. +7. **Work index:** Add section heading logic for featured cluster. +8. **Stretch:** Add `related` field support and render cross-links on detail pages. --- ## Open questions -1. Are `forms`, `messaging`, and `forms-lab` going to be public repos in the Flexion org? If not, which data approach (A–D) do we want? +1. ~~Are the repos going to be public?~~ **Resolved** — yes, all going public. Approach A confirmed. 2. What is Forms Lab's relationship to Forms — companion sandbox, docs site, or independent product? 3. Should the featured section on the home page have its own heading/intro copy, or just the grid? 4. Do we want a cap on featured projects (e.g., max 6) to protect the home page layout? From 5c3fc195f143d3083ee1ff441f546d4142fd70a1 Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Tue, 28 Apr 2026 22:04:39 +0000 Subject: [PATCH 3/5] =?UTF-8?q?docs:=20resolve=20Forms/Forms=20Lab=20relat?= =?UTF-8?q?ionship=20=E2=80=94=20separate=20but=20linked?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both are standalone featured items. Cross-linked via `related` field on detail pages and placed adjacent in the home grid. Promote related field from stretch to main implementation sequence. --- docs/featured-content.md | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/docs/featured-content.md b/docs/featured-content.md index 7ca59aa..a9539c2 100644 --- a/docs/featured-content.md +++ b/docs/featured-content.md @@ -105,7 +105,7 @@ Featured repos get a subtle highlight (accent border, background tint) but stay Each featured repo already gets a full detail page from the existing `WorkDetail` template. No structural change needed there. But consider: -- **Cross-linking between featured projects.** If Forms and Forms Lab are related, a "Related projects" section at the bottom of each detail page would help visitors discover the family. This can be driven by a `related: [forms-lab]` field in the overlay front-matter. +- **Cross-linking between related projects.** Forms and Forms Lab should cross-link on their detail pages via a "Related projects" section. Driven by a `related: [forms-lab]` field in overlay front-matter. This is low-cost and makes the relationship explicit without conflating the two. - **"Back to featured" breadcrumb vs. "Back to all work."** Since featured repos are *part of* the catalog, the breadcrumb should go to `/work/` (the full index), not to a separate featured page. This reinforces the "same collection" principle. --- @@ -128,15 +128,20 @@ Each featured overlay should communicate: --- -## What "Forms Lab" is relative to "Forms" +## Forms and Forms Lab: linked but separate -This needs clarification for content purposes: +Forms and Forms Lab are **separate featured items** — each gets its own card, detail page, and overlay. But they should be visibly linked so visitors understand the relationship. -- If Forms Lab is an **experimental playground** (try components, submit designs), it should be positioned as a companion/sandbox — "where agencies experiment before committing to Forms." -- If it's a **documentation/demo site**, it's more of a resource than a product — maybe not "featured" on its own but linked from the Forms detail page. -- If it's a **separate product** (different use case, different audience), it deserves its own full overlay with distinct positioning. +### How to surface the link -The layout accommodates any of these — the question is purely content/positioning. +| Mechanism | Where it appears | Effort | +|-----------|-----------------|--------| +| **`related` field in overlay front-matter** | Detail page — "Related projects" section at bottom | New field + render logic | +| **Explicit mention in body copy** | Detail page body (e.g. "See also [Forms Lab](/work/forms-lab/)") | Zero code, just content | +| **Shared "family" tag or badge** | Cards and detail pages | New taxonomy concept — probably overkill | +| **Adjacent placement in featured grid** | Home page | Just ordering, no code | + +**Recommendation:** Use the `related` front-matter field (planned for stretch anyway) to cross-link them on detail pages, and place them adjacent in the featured grid on the home page. The body copy can also reference the sibling naturally. This gives multiple touchpoints without introducing new taxonomy. --- @@ -178,20 +183,20 @@ No dead ends. Featured content is always reachable from the catalog, and the cat ## Implementation sequence 1. **Rename:** `content/work/messaging.md` → `content/work/flexion-notify.md` (update title/summary to reflect repo name if needed). -2. **Content:** Write `content/work/forms-lab.md`. Add `highlights` field to all four overlays. +2. **Content:** Write `content/work/forms-lab.md`. Add `highlights` field to all four overlays. Add `related` field to Forms and Forms Lab overlays. 3. **Overrides:** Mark all four repos as `featured: true`, `tier: active`, `category: product` in `overrides.yml`. 4. **Refresh:** Trigger manual catalog refresh (or wait for daily run) once repos are public, to populate `repos.json`. 5. **Component:** Build `FeaturedCard` component (or extend `RepoCard` with a `variant` prop). -6. **Home page:** Wire `FeaturedCard` into the existing `home-featured__grid`. +6. **Home page:** Wire `FeaturedCard` into the existing `home-featured__grid`. Place Forms and Forms Lab adjacent. 7. **Work index:** Add section heading logic for featured cluster. -8. **Stretch:** Add `related` field support and render cross-links on detail pages. +8. **Related projects:** Add `related` field support to overlay front-matter and render cross-links on detail pages (needed for Forms ↔ Forms Lab link). --- ## Open questions 1. ~~Are the repos going to be public?~~ **Resolved** — yes, all going public. Approach A confirmed. -2. What is Forms Lab's relationship to Forms — companion sandbox, docs site, or independent product? +2. ~~Forms Lab's relationship to Forms?~~ **Resolved** — separate featured items, but linked visibly (cross-links on detail pages, adjacent in grid). 3. Should the featured section on the home page have its own heading/intro copy, or just the grid? 4. Do we want a cap on featured projects (e.g., max 6) to protect the home page layout? 5. For the `highlights` field — should these be auto-derived from the overlay body, or always hand-authored? From c3d5c77f5a1bb2066dd9e5a02f9783c6e7d341dc Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Tue, 28 Apr 2026 22:47:11 +0000 Subject: [PATCH 4/5] feat: implement featured content section with enhanced cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename messaging.md → flexion-notify.md to match repo name - Create forms-lab.md overlay content - Add highlights and related fields to overlay front-matter - Extend Overlay type and parser to support highlights/related - Mark forms, forms-lab, document-extractor, flexion-notify as featured - Build FeaturedCard component with title, summary, highlights, and CTA - Wire FeaturedCard into home page with intentional ordering (Forms and Forms Lab adjacent) - Adjust featured grid: 2 cols at medium, 4 cols at wide - Add "Featured" / "All projects" section headings to work index - Implement related projects cross-links on detail pages (Forms ↔ Forms Lab) - Update work-detail tests for new catalog prop --- content/work/document-extractor.md | 4 ++ .../work/{messaging.md => flexion-notify.md} | 6 +- content/work/forms-lab.md | 26 ++++++++ content/work/forms.md | 6 ++ data/overrides.yml | 20 ++++++ src/build/entry.tsx | 2 +- src/catalog/overlays.ts | 8 +++ src/catalog/types.ts | 2 + src/design/components/featured-card/index.tsx | 26 ++++++++ .../components/featured-card/styles.css | 61 +++++++++++++++++++ src/design/index.css | 1 + src/design/layout.css | 30 ++++++++- src/pages/home.tsx | 13 +++- src/pages/work/detail.tsx | 41 ++++++++++++- src/pages/work/index.tsx | 20 ++++-- tests/views/work-detail.test.tsx | 10 +-- 16 files changed, 260 insertions(+), 16 deletions(-) rename content/work/{messaging.md => flexion-notify.md} (85%) create mode 100644 content/work/forms-lab.md create mode 100644 src/design/components/featured-card/index.tsx create mode 100644 src/design/components/featured-card/styles.css diff --git a/content/work/document-extractor.md b/content/work/document-extractor.md index fd9fb45..306d36a 100644 --- a/content/work/document-extractor.md +++ b/content/work/document-extractor.md @@ -1,6 +1,10 @@ --- title: Document Extractor summary: Turn PDFs and images of forms into structured data — without vendor lock-in. +highlights: + - Scanned PDFs, photos, and faxes + - Open alternative to commercial OCR + - Deploy, audit, and extend in-house --- ## What it solves diff --git a/content/work/messaging.md b/content/work/flexion-notify.md similarity index 85% rename from content/work/messaging.md rename to content/work/flexion-notify.md index e026d1e..bd33065 100644 --- a/content/work/messaging.md +++ b/content/work/flexion-notify.md @@ -1,6 +1,10 @@ --- -title: Messaging +title: Notify summary: A public-sector-grade platform for sending SMS and email notifications about benefits, deadlines, and outages. +highlights: + - SMS and email from one API + - Self-hosted, auditable delivery + - No per-message vendor pricing --- ## What it solves diff --git a/content/work/forms-lab.md b/content/work/forms-lab.md new file mode 100644 index 0000000..50b47ee --- /dev/null +++ b/content/work/forms-lab.md @@ -0,0 +1,26 @@ +--- +title: Forms Lab +summary: An LLM-assisted platform that turns PDF forms into structured specs and delivers them as accessible digital experiences. +highlights: + - PDF-to-spec extraction via LLM + - Static and conversational delivery modes + - Pluggable model variants per step +related: + - forms +--- + +## What it solves + +Government forms are trapped in PDFs. Manually converting them to digital experiences is slow, expensive, and error-prone. Forms Lab automates the pipeline: upload a PDF, extract a structured spec, and deliver a form experience — static or conversational — without hand-coding each field. + +## Who it's for + +Teams exploring how LLMs can accelerate form digitization. The platform separates *what to collect* from *how to present it*, so extraction and delivery can evolve independently. + +## Status + +Active. Deployed with branch-per-experiment architecture and a live demo catalog documenting findings on extraction accuracy, model selection, and presentation strategies. + +## Get started + +The repository includes a live demo, experiment catalog, and architecture documentation. See the README for deployment instructions. diff --git a/content/work/forms.md b/content/work/forms.md index 213e7bc..676f8ad 100644 --- a/content/work/forms.md +++ b/content/work/forms.md @@ -1,6 +1,12 @@ --- title: Forms summary: Accessible, USWDS-aligned form experiences that work for every resident and every agency. +highlights: + - WCAG 2.1 AA conformant + - Multi-step with save and resume + - Agency-agnostic, works with any back-end +related: + - forms-lab --- ## What it solves diff --git a/data/overrides.yml b/data/overrides.yml index 72d7250..40980a6 100644 --- a/data/overrides.yml +++ b/data/overrides.yml @@ -1,2 +1,22 @@ # Hand-authored overrides keyed by repo name. # See docs/catalog.md for supported fields. + +forms: + tier: active + category: product + featured: true + +forms-lab: + tier: active + category: product + featured: true + +document-extractor: + tier: active + category: product + featured: true + +flexion-notify: + tier: active + category: product + featured: true diff --git a/src/build/entry.tsx b/src/build/entry.tsx index 7e96b9a..29f9289 100644 --- a/src/build/entry.tsx +++ b/src/build/entry.tsx @@ -109,7 +109,7 @@ async function render( return renderToHtml() case 'work-detail': { const entry = catalog.find((e) => e.name === route.slug)! - return renderToHtml() + return renderToHtml() } } } diff --git a/src/catalog/overlays.ts b/src/catalog/overlays.ts index 49299f2..890a59d 100644 --- a/src/catalog/overlays.ts +++ b/src/catalog/overlays.ts @@ -18,6 +18,8 @@ export async function loadOverlay(path: string): Promise { return { title: stringOrUndefined(frontMatter.title), summary: stringOrUndefined(frontMatter.summary), + highlights: stringArrayOrUndefined(frontMatter.highlights), + related: stringArrayOrUndefined(frontMatter.related), body: body || undefined, } } @@ -25,3 +27,9 @@ export async function loadOverlay(path: string): Promise { function stringOrUndefined(value: unknown): string | undefined { return typeof value === 'string' ? value : undefined } + +function stringArrayOrUndefined(value: unknown): string[] | undefined { + if (!Array.isArray(value)) return undefined + const strings = value.filter((v): v is string => typeof v === 'string') + return strings.length > 0 ? strings : undefined +} diff --git a/src/catalog/types.ts b/src/catalog/types.ts index 33bddd0..9bfac90 100644 --- a/src/catalog/types.ts +++ b/src/catalog/types.ts @@ -34,6 +34,8 @@ export type OverrideEntry = { export type Overlay = { title?: string summary?: string + highlights?: string[] + related?: string[] body?: string } diff --git a/src/design/components/featured-card/index.tsx b/src/design/components/featured-card/index.tsx new file mode 100644 index 0000000..de833b0 --- /dev/null +++ b/src/design/components/featured-card/index.tsx @@ -0,0 +1,26 @@ +import type { CatalogEntry } from '../../../catalog/types' +import { url } from '../../../build/config' + +export function FeaturedCard({ entry, basePath }: { entry: CatalogEntry; basePath: string }) { + const href = url(`/work/${entry.name}/`, basePath) + const title = entry.overlay?.title ?? entry.name + const summary = entry.overlay?.summary ?? entry.description ?? '' + const highlights = entry.overlay?.highlights + + return ( + + ) +} diff --git a/src/design/components/featured-card/styles.css b/src/design/components/featured-card/styles.css new file mode 100644 index 0000000..5a40a39 --- /dev/null +++ b/src/design/components/featured-card/styles.css @@ -0,0 +1,61 @@ +.featured-card { + padding: var(--space-5); + border: 1px solid var(--color-surface-alt); + border-block-start: 3px solid var(--color-accent); + border-radius: var(--radius-md); + background: var(--color-surface); + box-shadow: var(--shadow-card); + display: grid; + gap: var(--space-3); + align-content: start; +} + +.featured-card__title { + font-size: var(--step-1); + line-height: 1.2; +} + +.featured-card__title a { + color: var(--color-ink); + text-decoration: none; +} + +.featured-card__title a:hover { + color: var(--color-link-hover); +} + +.featured-card__summary { + color: var(--color-ink-subtle); + font-size: var(--step--1); +} + +.featured-card__highlights { + list-style: none; + padding: 0; + display: flex; + flex-direction: column; + gap: var(--space-1); + font-size: var(--step--1); +} + +.featured-card__highlights li::before { + content: "\2713\0020"; + color: var(--color-tier-active); + font-weight: 700; +} + +.featured-card__cta { + margin-block-start: auto; + font-size: var(--step--1); +} + +.featured-card__cta a { + color: var(--color-link); + text-decoration: none; + font-weight: 600; +} + +.featured-card__cta a:hover { + color: var(--color-link-hover); + text-decoration: underline; +} diff --git a/src/design/index.css b/src/design/index.css index f807e12..8105289 100644 --- a/src/design/index.css +++ b/src/design/index.css @@ -13,6 +13,7 @@ @import url("./components/header/styles.css") layer(components); @import url("./components/footer/styles.css") layer(components); @import url("./components/repo-card/styles.css") layer(components); +@import url("./components/featured-card/styles.css") layer(components); @import url("./components/standards-list/styles.css") layer(components); @import url("./components/catalog-filter/styles.css") layer(components); @import url("./components/sortable-table/styles.css") layer(components); diff --git a/src/design/layout.css b/src/design/layout.css index 56c2a67..bc8db33 100644 --- a/src/design/layout.css +++ b/src/design/layout.css @@ -74,13 +74,41 @@ container-type: inline-size; } @container (min-width: 48rem) { - .home-featured__grid { grid-template-columns: repeat(3, 1fr); } + .home-featured__grid { grid-template-columns: repeat(2, 1fr); } .home-stats__grid { grid-template-columns: repeat(3, 1fr); } .work-index__list { grid-template-columns: repeat(2, 1fr); } } @container (min-width: 72rem) { + .home-featured__grid { grid-template-columns: repeat(4, 1fr); } .work-index__list { grid-template-columns: repeat(3, 1fr); } } + .work-index__section-heading { + grid-column: 1 / -1; + list-style: none; + } + .work-index__section-heading h2 { + font-size: var(--step-0); + color: var(--color-ink-subtle); + border-block-end: 1px solid var(--color-surface-alt); + padding-block-end: var(--space-2); + } + .work-detail__related { + margin-block-start: var(--space-6); + padding-block-start: var(--space-5); + border-block-start: 1px solid var(--color-surface-alt); + } + .work-detail__related ul { + margin-block-start: var(--space-3); + padding-inline-start: 0; + list-style: none; + display: flex; + flex-direction: column; + gap: var(--space-2); + } + .work-detail__related span { + color: var(--color-ink-subtle); + font-size: var(--step--1); + } .work-detail { display: grid; gap: var(--space-6); diff --git a/src/pages/home.tsx b/src/pages/home.tsx index f8b5857..d76ab87 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -1,5 +1,5 @@ import { Layout } from '../design/common/layout' -import { RepoCard } from '../design/components/repo-card' +import { FeaturedCard } from '../design/components/featured-card' import type { Catalog } from '../catalog/types' import { url } from '../build/config' import type { SiteConfig } from '../build/config' @@ -15,7 +15,14 @@ export function Home({ hero: HeroContent config: SiteConfig }) { - const featured = catalog.filter((e) => e.featured && !e.hidden) + const featuredOrder = ['forms', 'forms-lab', 'document-extractor', 'flexion-notify'] + const featured = catalog + .filter((e) => e.featured && !e.hidden) + .sort((a, b) => { + const ai = featuredOrder.indexOf(a.name) + const bi = featuredOrder.indexOf(b.name) + return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi) + }) const visible = catalog.filter((e) => !e.hidden) const active = visible.filter((e) => e.tier === 'active').length const languages = new Set( @@ -33,7 +40,7 @@ export function Home({ diff --git a/src/pages/work/detail.tsx b/src/pages/work/detail.tsx index 8d811a6..064168e 100644 --- a/src/pages/work/detail.tsx +++ b/src/pages/work/detail.tsx @@ -4,15 +4,18 @@ import { Link } from '../../design/components/link' import { Tag } from '../../design/components/tag' import { StandardsList } from '../../design/components/standards-list' import { evaluateRepo } from '../../catalog/repo-checks' -import type { CatalogEntry } from '../../catalog/types' +import type { Catalog, CatalogEntry } from '../../catalog/types' +import { url } from '../../build/config' import type { SiteConfig } from '../../build/config' export function WorkDetail({ entry, + catalog, now, config, }: { entry: CatalogEntry + catalog: Catalog now: Date config: SiteConfig }) { @@ -43,6 +46,7 @@ export function WorkDetail({ {entry.overlay?.body ? raw(entry.overlay.body) :

{renderBody(entry)}

} +