Sticky Title
The page title (layer `UxPageTitle` / Fable `PageHeader`, not the actor headers) joins c104's sticky band, which becomes two rows with scroll-direction modes.
New in this cycle: The page title joins the sticky band: the real UxPageTitle pins on row 1 beside the Filter and sub navs pin in row 2; scrolling down compacts title (15px), Filter (values only) and pills, scrolling up grows them to an in-between set (title 20px); a two-layer shadow lifts the band; nothing in the band moves the page (every mode flip Δ0)
Why
c104 pins the global filter and jump pills as one band. The page title still scrolls away, so a long page loses its name. Mark wants the title in the band: compact while scrolling down ("tidy, out of the way"), with room under it for sticky sub navs, and an in-between set while scrolling up.
Objective
Row 1 = the compact title (crumbs + title) and the filter; row 2 = any sub nav that registers. Modes rest · down · up by scroll direction. Nothing in the band may move page content.
Scope
- Layer:
useStickyBandrows + mode;UxStickyBandrow 1;UxPageTitleandNavJumpPillsjoin a mounted band automatically (opt-out); a compactFilterlook. - Fable: no page changes beyond the layer; SessionReading's switch joins row 2.
- Not the actor/hero headers.
Key Changes
Phase 0 mock (2026-10-02): /pages/sticky-title, design-local (useStickyTitleBand, components/sticky-title/). Verified headless: detail band 91 px down · 129 px up, listing 53 · 81, headers pin at the band's height in every mode, jumps land under the compact band, every mode flip Δpage 0 · Δcontent 0. Mark's mid-build question (no toggle — detect sub navs) → registration, and components auto-join a mounted band in Phase 1. Details in plan.md § Phase 0.
Phase 0 rework (2026-10-02): the real UxPageTitle pins instead of a teleported copy (Mark: use the actual title, as the Filter is used): a zero-height sticky box with a bottom margin reserving its rest height, restyled by mode. Re-verified, every flip Δ0 (after fixing a mid-animation rest measurement that moved the page 8 px). Fable's PageHeader wrapper needs flattening in the port. plan.md § Phase 0 rework.
Mock refinements + cleanup (2026-10-02):
- Rest title: at Fable
PageHeadersize (28px, modelled on:3032/inventory/harnesses), on both page kinds. - No load jump: the title box is an ordinary block at rest and freezes its own height only while pinned; the first paint (no JS) matches the loaded page to the pixel.
- Band shadow (Mark): two gradients off the backing's bottom edge, 5px at 0.18 and 20px at 0.08, black 100% → 0% alpha.
- Code cleanup:
- shared geometry constants in the composable;
- the Band owns publishing (
attach); - the Filter plumbing moved to
StickyTitleFilterBar(a replica of Fable'sGlobalFilterBar); - names say what they do (
titlePinned,reportTitle,showBacking); - each file documents its role and its Phase 1 counterpart.
- Re-verified headless after a dev-server restart (its watcher had stopped tracking the directory): every flip Δ0, headers under the band, jumps 16px under the compact band, no console errors.
Design refinements + documentation (2026-10-02):
- Hairline kept while pinned (Mark: "keep the hairline on the title … during scroll"); its padding tightens to 6px to fit the row.
- Motion (Mark: "slow down the transitions, especially when scrolling up … quint-in-out"; then "slow to 1s, 2s"): one easing,
cubic-bezier(0.83, 0, 0.17, 1). Compacting takes 1s and growing takes 2s, published as--st-band-dur/--st-band-easeand read by the backing, title, pills and table headers. Sampled headless: the band goes 129 → 91px over ~1s scrolling down, and 93 → 129px over ~2s scrolling up. - Captures:
scripts/shot-programs/c106-sticky-title.json(thread Phase 0 mock, subjectpage/sticky-title), 8 states: detail rest / down / up / jump / filter-compact off; listing rest / down / up. Files arepublic/cycles/design/c106-*.png. - Archive:
2026-10-02-c106-sticky-title.
Phase 1 + 2 — the layer and its docs (2026-10-02):
- Layer
6283ed9(q-nuxt-layer, local, unreleased):useStickyBandis rebuilt around roles (claimTitle/registerFilter/registerNav) and two layouts.- c104 (default): the filter and the pills side by side. Current consumers are unchanged, apart from the new shadow and the 400ms fade.
- c106 (
<UxStickyBand title>): the two-column band, with the scroll-direction mode andSTICKY_BAND.upRestyles = false. UxPageTitle: pins, freezes, restyles and settles; newsticky(opt-out) androwMinHeight(replaces wrapper boxes).NavJumpPills: joins any mounted band; docks under the title and compacts.Filter:bandCompact.UxTableListHeader: eases with the band.STICKY_BAND: holds geometry and motion.
- The band chooses the layout, not the pages. The Fable app reads the layer live; with title-joining tied to the band's
titleprop, its pages keep the c104 band until the Phase 4 port addstitleto its layout and flattensPageHeader. - Mock on the layer (design
87e0c1ad):/pages/sticky-titlenow composes the layer pieces exactly as the Fable shell will. The design-local replica is deleted. Same numbers verified: detail 81 → 90px (pills docked 48–80), listing 81 → 53px, up draws as down, jumps 16px under the band, every flip Δ0, no errors. - Docs (design
13a56842):UxStickyBandis now the sticky-band guide — a live framed demo, setup, layouts, members, scrolling, consumer rules, variables andSTICKY_BAND.UxPageTitle,NavJumpPills,FilterandUxTableListHeaderdocument their band behaviour. - Captures:
scripts/shot-programs/c106-sticky-title-p1.json(thread Phase 1 — on the layer), 8 mock states (c106-p1-*) plus the band guide (component/ux-sticky-band). The Phase 0 captures stay as the earlier thread. - Dev-server lesson: a fresh page load can be served stale while open tabs get hot updates. Removing a layer composable export needs a restart (Nuxt's auto-import list goes stale). Recorded in memory.
- Not yet: Phase 3 (release 0.9.26) and Phase 4 (Fable port;
:3032was down at the time, so Fable is unverified against the new layer — its dev server will need a restart).
Phase 3 + 4 — release and the qdash port (2026-10-02):
- q-nuxt-layer 0.9.26 (
d52aa1c, tag pushed, publish green; migration notes in the layer CLAUDE.md). Lockfiles at 0.9.26: design5d34901d, bob-wirecc9daed, app.qpoint.ioa7a231a, www28fbce3, qflowe53ab0c, qcontrol ui2dacfeaa. - Port target moved: c107 made the Fable app
qcontrol/crates/qdash/ui(feat/new-pages, PR #713,:3030). Portd1f2e00d, local:<UxStickyBand title>in the layout;PageHeadera fragment (the title a direct child of the page;rowMinHeight44; an emptymb-10keeps the spacing);- 13 detail pages drop
:titleSize="15"(full size at rest).
- c108 was live in the same tree. My one-line layout change went in beside its uncommitted banner, and c108 committed first (
f76fa091), so the port sits on top of it. - Verified on a temp server (an APFS clone at
../.c106tmpon:3033, the real layer, the:7882seed —:3030served stale modules and isn't this session's):- listing pinned band 53px;
- Team detail 90px, pills docked 48–80, table header at 90;
- a jump lands 16px under the band; the page never moves; no errors;
npm test566 + bible-check clean.
- Caught on the way: a scripted
PageHeaderrewrite split the file at the inner#right</template>and left invalid Vue. Tests and bible-check don't compile it; the temp server did, and an SFC compile check now runs overPageHeader, the layout and the 42 pages that use it. - Open:
- Detail pages now show the entity name twice at full size — the page title and the actor header's hero. Mark to rule.
- Push the qcontrol and design commits; deploy the design site; restart
:3030to see the port in the live app.
Polish on the live app — 0.9.27 (2026-10-02): Mark's pass on the ported band, tuned on the :3033 temp server.
- The backing fades in at its height instead of sliding down from the top; in a titled band it waits for the title, and its height animates only once fully in (layer
6f5b95c). - The title shrinks on its own clock: Mark tried slower (2s), then faster; 500ms (
STICKY_BAND.motion.title,--q-band-title). Settling back to rest stays 1s. - Look: shadow 7px at 0.11 + 28px at 0.08 (taller, then lighter); backing
surface-sunken-subtle, a tick off the page (lighter in light mode, darker in dark); 16px under docked pills (navBottomPad, was 12; band 94px with pills, 54px without). Layer04e4406. - q-nuxt-layer 0.9.27 (
be148b3, tag pushed, publish green). Lockfiles refreshed in all six consumers.
Outcome
Completed 2026-10-02. The page title joins the sticky band. <UxStickyBand title> turns on the two-column band: the real UxPageTitle pins beside the filter at 15px with its hairline, the jump pills dock under it as an outlined compact bar, and the filter drops its labels. Nothing in the band moves the page. It shipped in q-nuxt-layer 0.9.26, with Mark's polish on the live app in 0.9.27. qdash's PageHeader is a fragment, so the title is a direct child of the page and pins on every page (d1f2e00d, pushed to feat/new-pages). The UxStickyBand docs page is the guide, with the mock running live in a frame.
- Motion: quint-in-out, 1s to compact; the title shrinks on its own 500ms clock; the backing fades in over 400ms at its height and only then animates; the pills dock in 200ms. Scrolling up draws the same as scrolling down (
STICKY_BAND.upRestyles, kept off). - Look: the backing a tick off the page (
surface-sunken-subtle); a two-layer shadow (7px at 0.11, 28px at 0.08); 16px under the docked pills; listing band 54px, with pills 94px. - Ruled: detail pages show the entity name twice at full size (page title and actor header). Mark: fine.
- Captures: Phase 0 (design-local mock), Phase 1 (on the layer, plus the guide) and Phase 4 (in qdash at 0.9.27,
c106-qdash-*).
Learnings:
- A sticky element must not change its own flow height. Whatever pins freezes its rest height and restyles inside it; the filter is a zero-height box; the backing is out of flow. Every mode flip was measured with Δpage 0.
- Use the real component. A teleported copy of the title fought the original. The filter's technique, a sticky box that never moves the flow, worked for the title too.
- Docking beats sizing ahead. The backing grows when the pills arrive, not before, so it never covers them on the way in.
- Fresh loads can be served stale while open tabs hot-update. Restart before verifying; a removed auto-import export needs a restart. For a tree another session is using, verify on an APFS clone that shares the real layer (
cp -Rc; a symlinked layer path fails Vite's fs.allow). - Split a scripted Vue rewrite on the root's closing tag, not the first
</template>. Tests and the bible check don't compile SFCs, so add a compile check.
Path change (c107, 2026-10-02). The Fable app is now ../qcontrol/crates/qdash/ui on branch feat/new-pages. Its dev server is :3030 (launch config qdash-app) and the seed backend is :7882 (qdash-seed). The qcontrol-c99-fable worktree, its c99/claude-code-fable-5.1 working branch and :3032 are gone. Read every apps/claude-code-fable-5.1/… path in this cycle as crates/qdash/ui/…, the same files moved. The c99 rule that kept src/ read-only applied to the parallel apps and ended with the cutover. Commit to feat/new-pages, never main. See c107.
Visual record
23 captures — heroes, page slices, and full-page renders. Click to view.
Phase 4 — in qdash (0.9.27)
2026-10-02· 5 captureslatest




Phase 1 — on the layer
2026-10-02· 10 captures









Phase 0 mock
2026-10-02· 8 captures






