Skip to content

Commit 5181931

Browse files
hackmodscursoragent
andcommitted
refactor(public): ship fluid UI Gaps/QOL pass
GuideTipItem ReactNode, CatalogCard titleAs/href, PublicHubPanel, tip-pin guards, denser tools catalog, social map and captions polish, PartFrame fluid titles, Steward-101 dual chrome documented. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent e379992 commit 5181931

17 files changed

Lines changed: 229 additions & 154 deletions

File tree

‎.cursor/rules/guide-layout-standards.mdc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Import from `@/components/comms/guide-ui` only for public guide pages (no dual `
2323
| `GuideAccentBlock` | Nested h3/h4 accent strips inside a chapter |
2424
| `GuideWorkshopNote` | Facilitator / attendee workshop callouts |
2525
| `GuideCallout` | Notes inside guides (`measure="prose"` default; `fill` for full column) |
26-
| `GuideTrainingPhase` | Steward-101 numbered training modules only — not default playbook chrome |
26+
| `GuideTrainingPhase` | Steward-101 numbered training modules only — not default playbook chrome. **Dual system is intentional:** rounded phase cards for timed lessons vs border-l `GuideSection` for ordinary chapters. |
2727
| Playbook TOC | `GuidePlaybookToc` via GuideLayout — scroll-spy + smooth scroll + mobile details |
2828

2929
Playbooks: `preset="playbook"` → `readWide` + TOC rail. **Fill the article column** after the rail — do not leave a phone-width stack with empty right space.

‎docs/PROGRESS.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Progress Log
22

3+
## 2026-09-08 — Fluid UI Gaps / QOL pass
4+
5+
- [x] `GuideTipItem` ReactNode; `GuideCatalogCard` `titleAs`/`href`; `PublicHubPanel` (Brand Kit + onboarding)
6+
- [x] Tip `li.max-w-prose` layout guard; social map lg split; captions inset highlight; Tools catalog cards; PartFrame fluid titles
7+
- [x] Steward-101 dual chrome documented (GuideTrainingPhase vs GuideSection)
8+
- Deferred untouched: tool editor Cards, OL dark shell, legal, PDF, Hub/Portal
9+
- Session: [`session-knowledge-2026-09-08-fluid-ui-migration.md`](audit/session-knowledge-2026-09-08-fluid-ui-migration.md)
10+
- Verify: `npm run test:unit -- src/lib/comms/guide-layout-guards.test.ts`
11+
312
## 2026-09-08 — Fluid UI migration (Batches 1–12) complete
413

514
- [x] Complexity-grouped checklist + batch refactor of in-scope public guides/catalogs/onboarding/captions/brand-kit

‎docs/audit/guide-layout-growth-backlog.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,9 @@ See also closed in-scope items: [`docs/growth-backlog.md`](../growth-backlog.md)
5151
- Legal trio: leave narrow unless product asks for TOC playbooks.
5252
- Asset packs / tool panels: audit `border-l-2` duplication only if packs grow into multi-column catalogs.
5353
- PDF: keep [`guide-pdfs.mdc`](../../.cursor/rules/guide-pdfs.mdc); any “density” work stays in `pdf-layout` contracts.
54-
- **Fluid QOL (post-checklist):** `GuideTipItem` ReactNode content; `GuideCatalogCard` `titleAs` / linkable title; shared `PublicHubPanel`; guard against tip `li.max-w-prose`; optional Tools catalog densify; PartFrame fluid section titles; ToolEditor **chrome-only** fluid `h1` if product wants parity without touching canvas Cards.
54+
- **Fluid QOL (post-checklist) — shipped 2026-09-08:** `GuideTipItem` ReactNode; `GuideCatalogCard` `titleAs`/`href`; `PublicHubPanel`; tip-pin guards; social map lg split; Tools catalog cards; PartFrame fluid titles; Steward-101 dual chrome documented.
5555
- **Hub / Portal composition:** deferred product decision — largest remaining density debt if scope expands.
56+
- ToolEditor **chrome-only** fluid `h1` if product wants parity without touching canvas Cards (still deferred).
5657

5758
## Related
5859

‎docs/audit/session-knowledge-2026-09-08-fluid-ui-migration.md‎

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -53,24 +53,24 @@ Guards: `npm run test:unit -- src/lib/comms/guide-layout-guards.test.ts`.
5353

5454
Not blockers for “checklist complete.” Prioritized for a future pass (or growth backlog).
5555

56-
### High leverage (in-scope polish)
57-
58-
| Gap | Why it matters | Suggested fix |
59-
|-----|----------------|---------------|
60-
| **`GuideTipItem` is string-only** | Union-history (and peers) still hand-roll `<li className="min-w-0">` for `RegistryLink` cells | Allow `content: ReactNode` (or `GuideTipRichItem`) so tip grids stay consistent |
61-
| **`GuideCatalogCard` always uses `<h2>`** | Nested under chapter `h2`s → heading-level noise; playbooks often duplicate title text + same-label CTA | Optional `titleAs` / linkable title; or `title` as Link when `href` provided |
62-
| **Page-local hub panel chrome** | Brand Kit `BrandKitPanel` and onboarding stepped panel share the same gradient/border language but are copy-pasted | Extract `PublicHubPanel` (or move BrandKitPanel under `components/brand/`) for Brand Kit + onboarding reuse |
63-
| **No structural guard for tip pins** | `guide-layout-guards` only bans local `GuideSection` / dual imports — cannot catch `li.max-w-prose` regressions | Extend guards: forbid `max-w-prose` on tip/list items inside `src/app/**/guide/**/page.tsx` (allow section intro / GuideProse) |
64-
65-
### Medium (visual / steward feel)
66-
67-
| Gap | Why it matters | Suggested fix |
68-
|-----|----------------|---------------|
69-
| **Social map diagram still “cluster in a box”** | Wider than `max-w-xs`, but not full-column like the scale diagram | Optional side-by-side legend + cluster, or soft fill of `GuideWideFigure` |
70-
| **Steward-101 dual chrome** | `GuideTrainingPhase` cards vs `GuideSection` border-l elsewhere | Keep intentional for training, or document as the only dual system; avoid inventing a third |
71-
| **Captions highlight ring on border-l tiles** | Deep-link `?caption=` ring may feel clipped vs old Card | Spot-check mobile; consider `rounded-r-lg` + inset ring only |
72-
| **Tools catalog group lists** | Still sparse title+blurb rows (not catalog cards) after start-here share | Optional densify to `GuideCatalogCard` per tool if Tools feels thin next to Guides |
73-
| **Website PartFrame headings** | Still fixed `text-2xl` while rest of public uses fluid tokens | Apply `PUBLIC_SECTION_TITLE_CLASS` inside PartFrame without changing layout |
56+
### High leverage (in-scope polish) — **shipped 2026-09-08 QOL pass**
57+
58+
| Gap | Status |
59+
|-----|--------|
60+
| **`GuideTipItem` ReactNode content** | Done — union-history rich registry cells migrated |
61+
| **`GuideCatalogCard` `titleAs` / `href`** | Done — h3 under chapters; linkable title drops duplicate CTAs |
62+
| **`PublicHubPanel`** | Done — Brand Kit + onboarding share chrome |
63+
| **Tip `li.max-w-prose` guard** | Done — `guide-layout-guards.test.ts` |
64+
65+
### Medium (visual / steward feel) — **shipped 2026-09-08 QOL pass**
66+
67+
| Gap | Status |
68+
|-----|--------|
69+
| **Social map full-column** | Done — cluster + blind-spot side-by-side at `lg` |
70+
| **Steward-101 dual chrome** | Documented intentional — `GuideTrainingPhase` JSDoc + guide-layout-standards |
71+
| **Captions deep-link highlight** | Done — inset ring + rounded tile (mobile-safe) |
72+
| **Tools catalog densify** | Done — `GuideCatalogCard` per tool |
73+
| **Website PartFrame headings** | Done — `PUBLIC_SECTION_TITLE_CLASS` |
7474

7575
### Deferred / out-of-scope (do not “finish” under fluid checklist)
7676

‎src/app/[locale]/brand-kit/page.tsx‎

Lines changed: 16 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useRef, useState, type ReactNode } from "react";
3+
import { useRef, useState } from "react";
44
import { useTranslations } from "next-intl";
55
import { Link } from "@/i18n/navigation";
66
import { useBrandStore } from "@/store/brand-store";
@@ -35,50 +35,21 @@ import {
3535
} from "@/lib/constants/unionPresets";
3636
import { SafeLogoImage } from "@/components/brand/SafeLogoImage";
3737
import { UnionOpsMark } from "@/components/brand/UnionOpsMark";
38-
import { resolveLocalNumber, cn } from "@/lib/utils";
38+
import { resolveLocalNumber } from "@/lib/utils";
3939
import { isBrandThemeEstablished } from "@/lib/utils/brand-theme";
4040
import { ComposedPageLayout } from "@/components/layout/ComposedPageLayout";
4141
import { TOOL_COMPOSITION } from "@/lib/constants/page-composition";
42-
import {
43-
PUBLIC_CARD_TITLE_CLASS,
44-
PUBLIC_PAGE_TITLE_CLASS,
45-
} from "@/lib/constants/public-type";
42+
import { PUBLIC_PAGE_TITLE_CLASS } from "@/lib/constants/public-type";
4643
import {
4744
guideCtaClassSm,
4845
guideCtaOutlineClassSm,
4946
} from "@/components/comms/guideCtaClasses";
47+
import { PublicHubPanel } from "@/components/comms/PublicHubPanel";
5048
import { PresetSloganPicker } from "@/components/brand/PresetSloganPicker";
5149
import { WorkshopDemoPath } from "@/components/comms/WorkshopDemoPath";
5250
import { JointActionCard } from "@/components/comms/campaign/JointActionCard";
5351
import { useWorkshopDemoSession } from "@/hooks/use-workshop-demo-session";
5452

55-
function BrandKitPanel({
56-
title,
57-
description,
58-
children,
59-
className,
60-
}: {
61-
title?: string;
62-
description?: string;
63-
children: ReactNode;
64-
className?: string;
65-
}) {
66-
return (
67-
<section
68-
className={cn(
69-
"min-w-0 space-y-3 rounded-xl border border-opseu-blue/15 bg-gradient-to-b from-opseu-blue/[0.04] to-white p-4 sm:p-5",
70-
className,
71-
)}
72-
>
73-
{title ? <h2 className={PUBLIC_CARD_TITLE_CLASS}>{title}</h2> : null}
74-
{description ? (
75-
<p className="text-sm text-gray-600">{description}</p>
76-
) : null}
77-
{children}
78-
</section>
79-
);
80-
}
81-
8253
export default function BrandKitPage() {
8354
const t = useTranslations("brandKit");
8455
const nav = useTranslations("nav");
@@ -261,7 +232,7 @@ export default function BrandKitPage() {
261232
<BrandKitContextHint />
262233

263234
<div className="mt-4 grid min-w-0 items-start gap-4 lg:grid-cols-2 xl:grid-cols-3">
264-
<BrandKitPanel
235+
<PublicHubPanel
265236
title={t("unionPreset.title")}
266237
description={t("unionPreset.description")}
267238
className="lg:col-start-1 lg:row-start-1"
@@ -345,9 +316,9 @@ export default function BrandKitPage() {
345316
</div>
346317
</div>
347318
) : null}
348-
</BrandKitPanel>
319+
</PublicHubPanel>
349320

350-
<BrandKitPanel
321+
<PublicHubPanel
351322
title={t("currentSettings")}
352323
className="lg:col-start-2 lg:row-start-1 lg:row-span-2 xl:row-span-1"
353324
>
@@ -384,9 +355,9 @@ export default function BrandKitPage() {
384355
primaryLabel={t("colors.primary")}
385356
secondaryLabel={t("colors.secondary")}
386357
/>
387-
</BrandKitPanel>
358+
</PublicHubPanel>
388359

389-
<BrandKitPanel
360+
<PublicHubPanel
390361
title={t("logo.title")}
391362
description={t("logo.description")}
392363
className="lg:col-start-1 lg:row-start-2 xl:col-start-3 xl:row-start-1"
@@ -418,12 +389,12 @@ export default function BrandKitPage() {
418389
onCustomLogoClear={() => setBrandKit({ customLogoDataUrl: "" })}
419390
onLogoTextChange={(text) => setBrandKit({ logoText: text })}
420391
/>
421-
</BrandKitPanel>
392+
</PublicHubPanel>
422393
</div>
423394

424395
<div className="mt-4">
425396
{themeEstablished && unionPresetId === "opseu" ? (
426-
<BrandKitPanel
397+
<PublicHubPanel
427398
title={t("coalitionPreview.title")}
428399
description={t("coalitionPreview.description")}
429400
className="mb-4"
@@ -439,13 +410,13 @@ export default function BrandKitPage() {
439410
t("coalitionPreview.defaultBadge")
440411
}
441412
/>
442-
</BrandKitPanel>
413+
</PublicHubPanel>
443414
) : null}
444415
<BrandKitCanvasPanel />
445416
</div>
446417

447418
<div className="mt-4 grid gap-4 lg:grid-cols-2">
448-
<BrandKitPanel>
419+
<PublicHubPanel>
449420
<LocalLinksEditor
450421
websiteUrl={brandKit.websiteUrl ?? ""}
451422
facebookUrl={brandKit.facebookUrl ?? ""}
@@ -454,9 +425,9 @@ export default function BrandKitPage() {
454425
onFacebookChange={(url) => setBrandKit({ facebookUrl: url })}
455426
onCustomLinksChange={(links) => setBrandKit({ customLinks: links })}
456427
/>
457-
</BrandKitPanel>
428+
</PublicHubPanel>
458429

459-
<BrandKitPanel>
430+
<PublicHubPanel>
460431
<MembershipUrlsEditor
461432
membershipUrls={brandKit.membershipUrls ?? []}
462433
onChange={(urls) => setBrandKit({ membershipUrls: urls })}
@@ -465,7 +436,7 @@ export default function BrandKitPage() {
465436
brandKit.opseuSectorId,
466437
)}
467438
/>
468-
</BrandKitPanel>
439+
</PublicHubPanel>
469440
</div>
470441
</ComposedPageLayout>
471442
);

‎src/app/[locale]/captions/page.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,9 @@ function CaptionsPageContent() {
9191
key={template.id}
9292
id={`caption-${template.id}`}
9393
className={cn(
94-
"min-w-0 scroll-mt-24 border-l-2 border-opseu-blue/30 bg-white pl-4 pr-1 py-1 transition-shadow sm:pl-5",
94+
"min-w-0 scroll-mt-24 rounded-r-lg border-l-2 border-opseu-blue/30 bg-white py-2 pl-4 pr-2 transition-[box-shadow,background-color] sm:pl-5",
9595
highlighted &&
96-
"rounded-r-lg bg-opseu-blue/[0.04] ring-2 ring-opseu-blue shadow-sm",
96+
"bg-opseu-blue/[0.06] ring-2 ring-inset ring-opseu-blue",
9797
)}
9898
>
9999
<div className="flex items-start justify-between gap-3 sm:gap-4">

‎src/app/[locale]/guide/steward-playbooks/page.tsx‎

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ export default async function StewardPlaybooksPage({
148148
return (
149149
<GuideCatalogCard
150150
key={href}
151+
titleAs="h3"
152+
href={href}
151153
className={
152154
featured
153155
? "rounded-r-lg border border-opseu-blue/25 border-l-opseu-blue bg-opseu-blue/[0.06] py-3 pr-4"
@@ -156,11 +158,6 @@ export default async function StewardPlaybooksPage({
156158
title={t(`links.${key}`)}
157159
body={t(`blurbs.${key}`)}
158160
meta={featured ? t("quizBadge") : undefined}
159-
action={
160-
<Link href={href} className={guideCtaOutlineClass}>
161-
{t(`links.${key}`)} →
162-
</Link>
163-
}
164161
/>
165162
);
166163
},
@@ -197,13 +194,10 @@ export default async function StewardPlaybooksPage({
197194
{workspaceLinks.map(({ href, titleKey, blurbKey }) => (
198195
<GuideCatalogCard
199196
key={href}
197+
titleAs="h3"
198+
href={href}
200199
title={t(`workspaces.${titleKey}`)}
201200
body={t(`workspaces.${blurbKey}`)}
202-
action={
203-
<Link href={href} className={guideCtaOutlineClass}>
204-
{t(`workspaces.${titleKey}`)} →
205-
</Link>
206-
}
207201
/>
208202
))}
209203
</ul>

‎src/app/[locale]/guide/union-history/page.tsx‎

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
GuideActionRow,
2323
GuideBulletList,
2424
GuideCallout,
25+
GuideCatalogCard,
2526
GuideOutlineList,
2627
GuideOutlineStep,
2728
GuideProse,
@@ -311,16 +312,18 @@ export default async function UnionHistoryGuidePage({
311312
label={t("example.items.local.label")}
312313
content={t("example.items.local.content")}
313314
/>
314-
<li className="min-w-0 leading-relaxed">
315-
<span className="font-semibold text-opseu-dark">
316-
{t("example.items.council.label")}.
317-
</span>{" "}
318-
{t("example.items.council.before")}{" "}
319-
<RegistryLink id="nrlc-who-we-are">
320-
{t("example.items.council.link")}
321-
</RegistryLink>{" "}
322-
{t("example.items.council.after")}
323-
</li>
315+
<GuideTipItem
316+
label={t("example.items.council.label")}
317+
content={
318+
<>
319+
{t("example.items.council.before")}{" "}
320+
<RegistryLink id="nrlc-who-we-are">
321+
{t("example.items.council.link")}
322+
</RegistryLink>{" "}
323+
{t("example.items.council.after")}
324+
</>
325+
}
326+
/>
324327
<GuideTipItem
325328
label={t("example.items.area.label")}
326329
content={t("example.items.area.content")}
@@ -329,14 +332,18 @@ export default async function UnionHistoryGuidePage({
329332
label={t("example.items.union.label")}
330333
content={t("example.items.union.content")}
331334
/>
332-
<li className="min-w-0 leading-relaxed">
333-
<span className="font-semibold text-opseu-dark">
334-
{t("example.items.nupge.label")}.
335-
</span>{" "}
336-
{t("example.items.nupge.before")}{" "}
337-
<RegistryLink id="nupge">{t("example.items.nupge.link")}</RegistryLink>{" "}
338-
{t("example.items.nupge.after")}
339-
</li>
335+
<GuideTipItem
336+
label={t("example.items.nupge.label")}
337+
content={
338+
<>
339+
{t("example.items.nupge.before")}{" "}
340+
<RegistryLink id="nupge">
341+
{t("example.items.nupge.link")}
342+
</RegistryLink>{" "}
343+
{t("example.items.nupge.after")}
344+
</>
345+
}
346+
/>
340347
</GuideTipGrid>
341348
<GuideCallout className="mt-5">
342349
<p className="font-semibold text-opseu-dark">{t("tipLabel")}</p>
@@ -422,20 +429,17 @@ export default async function UnionHistoryGuidePage({
422429
</GuideSection>
423430

424431
<GuideSection id="tools" title={t("tools.title")} intro={t("tools.intro")}>
425-
<GuideTipGrid>
432+
<ul className="mt-4 grid list-none gap-5 p-0 sm:grid-cols-2">
426433
{toolRows.map(({ key, href }) => (
427-
<li key={key} className="min-w-0 leading-relaxed">
428-
<Link
429-
href={href}
430-
className="font-semibold text-opseu-blue underline underline-offset-2"
431-
>
432-
{t(`tools.items.${key}.label`)}
433-
</Link>
434-
{". "}
435-
{t(`tools.items.${key}.content`)}
436-
</li>
434+
<GuideCatalogCard
435+
key={key}
436+
titleAs="h3"
437+
href={href}
438+
title={t(`tools.items.${key}.label`)}
439+
body={t(`tools.items.${key}.content`)}
440+
/>
437441
))}
438-
</GuideTipGrid>
442+
</ul>
439443
<AffiliationMapWorksheetButton className="mt-5" />
440444
<GuideActionRow>
441445
<Link href="/tools/org-chart" className={guideCtaClass}>

0 commit comments

Comments
 (0)