Просмотр исходного кода

T11 sweep: dead CSS out, dialog contract normalised, docs finalised

The second cut of the convergence sweep:

- app.css: 42 dead rules (~150 lines) deleted with references
  verified against every page, engine and test bundle - the whole
  terminal surface (.terminal + the .term-entry family, dead since
  plan 17's event-log migration), the .quote-popover pair (app.js
  creates only .quote-button now), .log-task-link, .waiting-drafts,
  .yeehaw-card/.yeehaw-list (the live celebration is the log's
  .yeehaw-message row), .diff-*, .result-failed/-stdout/-output.
  Section headers kept and annotated; zero literal colours found
  (token discipline held); the four multi-location selectors
  (.chat-bubble/.log-header/.log-toolbar/.advisor-hero-actions) are
  intentional cascade overrides, left as they are.
- Dialog contract normalised: the goal/task/user/rule create dialogs
  carry .dialog-sm; both quote-comment dialogs restructured onto the
  shared titlebar/body/footer shape (inline styles gone). The
  project delete dialog keeps its bespoke confirm-by-name danger
  shape by design.
- docs/spry-conversion.md gains the final contracts: the subnav
  engine, .act-consult, work-stream row grammar, saved-view hash,
  dialog shape, CrumbView - the next person inherits contracts, not
  archaeology.
- docs/ux-notes.md: historical banner (the critique shipped; mocks
  were explorations, since superseded).
- mocks/ deleted: the four dashboard direction samplers - nothing
  in test/ or docs/ reads them; the shipped dashboard is the truth.
- Two app.css comments referencing the deleted samplers reworded.
saddle-ClankerSaddle 1 день назад
Родитель
Сommit
7479404470

+ 99 - 0
docs/spry-conversion.md

@@ -235,3 +235,102 @@ it — and (c) re-set the slot from any pulse refresh the page owns
 (see `RefreshTaskDataAction`). Both surfaces share ONE markup and
 (see `RefreshTaskDataAction`). Both surfaces share ONE markup and
 ONE engine by construction: never hand-copy the shell.
 ONE engine by construction: never hand-copy the shell.
 
 
+## The sub-nav engine (T11 — one implementation, every detail page)
+
+A detail page's anchor sub-nav is ONE shared engine, `initSubnav()`
+in `app.js`. T5 shipped an IntersectionObserver chassis inline on
+the task page and T6 a closest-to-fold highlighter inline on the
+goal page; T11 unified them — never hand-copy either again. The
+engine owns smooth jumps that stop below the sticky chrome (topbar
++ strip, measured at click time), the closest-to-fold section
+highlight (the section whose top sits closest above the strip's
+bottom edge — the two-column Act|Consult grid made the naive
+"last heading that crossed" rule light the wrong card), the
+`.is-stuck` strip class, `aria-current` on the active link, and
+publishing the measured topbar height as `--topbar-bottom` (the
+sticky log toolbar consumes the same property).
+
+Markup contract:
+
+    <nav class="subnav" aria-label="Sections" data-subnav>
+      <a href="#section-id">…</a>
+    </nav>
+    <section id="section-id" data-subnav-section="section-id">…
+
+Both attributes are required: the `id` is what an anchor jumps to,
+`data-subnav-section` is what the highlight matches. A page opts in
+with one call — `if (window.initSubnav) { window.initSubnav(); }` —
+which is idempotent (the engine binds its document-level listeners
+once, flagged on `<html>`, and re-runs only the sync pass from
+`statum.afterEvaluate`).
+
+## The Act|Consult column layout (T11 — one class)
+
+Both detail pages (task, goal) lay out acting surfaces left and
+consulting surfaces right with ONE class:
+
+    <div class="with-sidebar act-consult">
+      <div class="stack">…ACT…</div>
+      <div class="stack">…CONSULT…</div>
+    </div>
+
+`app.css` re-proportions Spry's `.with-sidebar` (act takes the
+flexible track, consult a bounded 18–24rem rail) and restores the
+framework's 55rem stack below the breakpoint so ACT renders fully
+above CONSULT on narrow screens — DOM order IS the stack order.
+The per-page `.task-columns` / `.goal-columns` variants were
+unified away; do not reintroduce them.
+
+## The work-stream row grammar (T10)
+
+The dashboard's work stream is ONE `<ul class="work-stream">` of
+list rows (badge → when → actor → text), each row's badge mapping
+to a semantic group per the badge contract: task progress → the
+success group (`.badge-stream-task_progress`), goal events → accent
+(`.badge-stream-goal_event`), log highlights → the quiet inset
+(`.badge-stream-log_highlight`). Rows are list items, never table
+rows — a stream is read down the left edge and wraps under itself
+when narrow.
+
+## The saved-view hash contract (T10)
+
+`/tasks` filter state lives in `location.hash` (client-owned,
+shareable): the page reads it on load and keeps it current as the
+chips change, so a saved URL restores the exact view. The keys are
+the filter names the tasks slot already carries (`#status=…`,
+combinable). No server round-trip — the hash IS the view.
+
+## The dialog contract (T4/T8/T11)
+
+Every create/answer dialog in the app follows ONE shape:
+
+    <dialog id="…" class="dialog-sm">
+      <div class="dialog-titlebar">
+        <p class="dialog-title">…</p>
+        <button type="button" class="dialog-close" aria-label="Close"
+                onclick="window.closeAppDialog('…')">×</button>
+      </div>
+      <form stm-action="…">
+        <div class="dialog-body form-rows">…fields…</div>
+        <div class="dialog-footer">…buttons right-aligned…</div>
+      </form>
+    </dialog>
+
+Open with `window.openAppDialog('…')` — it wraps
+`statum.onNotifyHandler` and closes the dialog on ANY notify (a
+success closes the modal; an error closes it too, the toast carries
+the message). An open dialog KEEPS its open state and typed values
+across pulse re-renders (statum never rebuilds static markup). The
+one intentional deviation is the project delete dialog, which keeps
+its bespoke confirm-by-name danger shape (T9's design).
+
+## The CrumbView contract (T11)
+
+Every detail page renders its breadcrumb from ONE class:
+`ClankerSaddle.CrumbView` (`src/Json.vala`) — `{href, label}`,
+`href` null on the leaf. Build the vector in the entrypoint,
+publish it as `crumbs` via `StateJson.list<CrumbView>(…)`, include
+the partial (`<pstm-fragment name="breadcrumb" crumbs="…"/>`). The
+four per-page copies (Task/Goal/Project/Chat CrumbView) were
+unified away; do not add a page-local one.
+

+ 8 - 0
docs/ux-notes.md

@@ -1,5 +1,13 @@
 # ClankerSaddle web UI — structural layout critique
 # ClankerSaddle web UI — structural layout critique
 
 
+> **Historical document (goal 41).** This critique drove the UI/UX
+> overhaul; every quick win and structural bet it proposed now ships
+> on the Spry framework — `docs/spry-conversion.md` describes the
+> app as built (the binding conventions), and this file stays as the
+> record of what was wrong and why. The `mocks/` direction samplers
+> it references were explorations and were deleted once the shipped
+> dashboard superseded them.
+
 This is an opinions document about **structure**, not looks: the theme,
 This is an opinions document about **structure**, not looks: the theme,
 colours and typography stay as they are. "Structure" here means page
 colours and typography stay as they are. "Structure" here means page
 hierarchy (what leads a page, what folds away), information order,
 hierarchy (what leads a page, what folds away), information order,

+ 0 - 177
mocks/README.md

@@ -1,177 +0,0 @@
-# Dashboard visual directions — static HTML samplers
-
-Three standalone, fully self-contained mocks of the SAME dashboard.
-No frameworks, no build step, no external assets — embedded CSS only
-(the brand emblem rides inline as a downscaled PNG data URI).
-Open directly via `file://`. Each supports light AND dark via
-`prefers-color-scheme` (verify by toggling devtools scheme emulation).
-
-- `dashboard-primer.html` — 1 Curated Primer (light-first)
-- `dashboard-control.html` — 2 Control Room (dark-first)
-- `dashboard-swiss.html` — 3 Swiss Flat (light-first)
-- `dashboard-current.html` — 4 Current quieted (theme-faithful)
-
-All four carry IDENTICAL content structure (the `<body>` markup is
-byte-identical apart from the sampler number/label), so the comparison
-is purely visual.
-
-## Header
-
-Every direction keeps the real app's chrome: the emblem + ClankerSaddle
-brand at the app's original `height: 8rem` (with its `-0.2rem`
-margin-block trim), and the three nav groups from `main.html` — Work
-(Dashboard, Tasks, Goals, Chats), Library (Projects, Rules, Stacks,
-Discoveries), Admin (Saddles, Users, Model) — plus the Account menu.
-Pure CSS `<details>` dropdowns, no JS. Brand and nav are centre-aligned
-on one axis (verified via engine geometry: brand/emblem/nav/summary
-mids equal to 0.1px in all three directions). The emblem is embedded
-per file as a 256px, 64-colour-quantised PNG data URI (~31 KB base64)
-so the samplers stay standalone while rendering crisp at 8rem.
-
-## Content model (per the approved hierarchy)
-
-- Zone 1 (top): the advisor entry box ONLY — heading and description
-  sit INSIDE the composer card, above the textarea; model/snapshot
-  hint and Start chat below. The front page never shows chat
-  transcripts themselves (review decision). A single quiet link to
-  the chats list.
-- Zone 2 (one small scroll below): "Needs a human" items tiered ABOVE
-  queue health — a completion awaiting review, a turn-cap parked task
-  to wake, an open question; then running / stuck / queued counts and
-  saddle states.
-- Zone 3 (ambient, reduced density): notes, latest discoveries,
-  active tasks table and activity timeline — ALL sections start
-  collapsed, rendered as plain ruled expander rows with NO card
-  chrome (no background, no border box, no radius).
-
-Sample data uses the real app's vocabulary: statuses
-draft / queued / assigned / working / blocked / review_requested /
-suspended / done / failed; saddle states idle / busy / offline;
-blocks review / question; turn-cap parks. Task titles reference real
-recent fleet work. Sections are deliberately uneven — one saddle
-offline, one task with no latest progress, a sparse notes list —
-because perfection reads fake.
-
-## Direction 1 — Curated Primer
-
-GitHub/Primer lineage, disciplined, light default. Cards on a sunken
-page, 8px grid, 8px radius, tinted status pills, the existing blue as
-accent. The natural evolution of today's `app.css`.
-
-:root (light):
-```
---surface-sunken #f6f8fa   --surface #ffffff    --surface-inset #eff2f5
---ink #1f2328               --ink-muted #59636e  --ink-faint #6e7681
---line #d1d9e0              --line-soft #e4eaef
---accent #0969da            --accent-surface #ddf4ff
---success #1a7f37           --warning #9a6700    --danger #cf222e
---radius 8px
-```
-Dark variant: `--surface-sunken #0d1117`, `--surface #151b23`,
-`--surface-inset #1c2430`, `--ink #e6edf3`, `--ink-muted #9198a1`,
-`--ink-faint #6e7681`, `--line #3d444d`, `--line-soft #262c36`,
-`--accent #4493f8`, `--accent-surface #121d2f`, `--success #3fb950`,
-`--warning #d29922`, `--danger #f85149`.
-
-Status = tinted pill pairs: working/blocked/queued on yellow tints,
-done/progress green tints, failed red tint, review orange tint,
-question blue tint, parked/suspended neutral. Type: system-ui 15px.
-Shadows: none (1px borders only).
-
-## Direction 2 — Control Room
-
-Dark-first, mono-forward, mission-console restraint. Quiet cold
-neutrals; status colour carries the meaning; labels and numerals in
-mono. Status reads as small outline capsules in the status hue —
-colour IS the signal, fill stays quiet. Light variant is a working
-console on white, not an afterthought.
-
-:root (dark, default):
-```
---bg #0f1317   --surface #151a20   --raise #1c232b
---ink #c9d4de  --dim #7e8b98       --faint #525e6a
---rule #262e38 --rule-hi #394450
---run #4cc38a  --warn #f5b452  --crit #ef6460  --info #58a6ff
---accent #58a6ff  --radius 4px
---mono ui-monospace …  --sans system-ui …
-```
-Light variant: `--bg #eef1f4`, `--surface #ffffff`, `--raise #f4f6f8`,
-`--ink #1b2530`, `--dim #54626f`, `--faint #75828e`, `--rule #d7dee4`,
-`--rule-hi #b4c0ca`, `--run #10735a`, `--warn #8f5b00`,
-`--crit #c3362f`, `--info #0a5dc2`.
-
-Status semantics: run=green (running/done/busy), warn=amber
-(queued/blocked/review/stuck), crit=red (failed), info=blue
-(interactive/question). Type: sans 14px body, mono for labels,
-metrics, meta and times. Radius 4px; no shadows; saddle states are
-tiny square lamps (filled / outline / dashed).
-
-## Direction 3 — Swiss Flat
-
-Strict grid, typographic hierarchy, one strong accent, no shadows, no
-ornament. Structure lines are INK, not grey; the frame is the design.
-A hard left rail carries brand + zone labels; metrics are oversized
-light numerals (weight 200). Status is typography: weight and colour
-in near-black / greys, with red reserved for failure and the accent.
-
-:root (light):
-```
---paper #fdfdfb  --ink #111111  --grey-1 #6b6b6b  --grey-2 #82827e
---rule #111111   --rule-thin #d9d9d6
---red #e2231a    --radius 0
---font "Helvetica Neue" …  --mono ui-monospace …
-```
-Dark variant: `--paper #121212`, `--ink #f0f0ec`, `--grey-1 #a8a8a4`,
-`--grey-2 #7c7c78`, `--rule #f0f0ec`, `--rule-thin #2e2e2c`,
-`--red #ff4438`.
-
-Status: failed and the single accent share red; everything else is
-typographic (bold caps tags, question underlined in ink). Links are
-ink with a thin grey underline; red appears on hover and on the
-primary button. Radius 0, no shadows, 2px structural rules.
-
-## Direction 4 — Current quieted (theme-faithful)
-
-The control sample: today's theme kept as close as possible — the
-same tokens as `app.css` (`#f6f8fa` sunken page, `#ffffff` cards,
-`#0969da` accent, `1a7f37/9a6700/cf222e` status, 8px radius, 8px
-grid, system-ui 15px), the buttony nav dropdown menus, bordered cards
-with tinted pill badges, `details` sections rendered as cards, and the
-full-grid bordered tables. Only the LAYOUT is quieted:
-
-- one column at the app's real 64rem (no second sidebar column),
-- "Needs a human" as three rows inside ONE card instead of a card
-  per item,
-- section headings reduced to a single small heading each — no
-  stacked h2 + h3 pairs, no explanatory paragraphs between sections,
-- ambient sections are plain ruled expanders (the app's
-  details-as-cards chrome removed per review),
-- the advisor hero is a single card carrying its own heading and
-  description, matching `home.html`'s token recipe.
-
-Component styles verified identical to the live `app.css` via
-computed-style comparison (radius, shadow, paddings, colours, fonts).
-A dark set is included so it honours `prefers-color-scheme` like its
-siblings; the light values are the faithful ones.
-
-## Anti-slop checklist (honoured by all four)
-
-No purple-blue gradients, no glassmorphism, no oversized drop
-shadows, no marketing hero, no emoji decoration, no fake-perfect
-symmetry. Status palettes avoid the blue/purple wash; accent use is
-rationed.
-
-## Verification performed
-
-- Files open standalone via `file://` (Chromium 154 headless).
-- Light/dark flip verified per file via forced scheme: page
-  backgrounds and every status colour change to the intended tokens;
-  content extents identical between schemes (no reflow breakage).
-- The four `<body>` blocks verified identical modulo sampler label.
-- Nav dropdowns verified open (per-direction probe renders); emblem
-  verified visible in both schemes in all four directions, rendered
-  at the app's original 8rem height; brand/nav vertical alignment
-  verified equal via getBoundingClientRect mids.
-- Direction 4 verified component-faithful to `app.css` via computed
-  styles (radius/shadow/padding/colours/fonts all equal).
-- 390px mobile: no horizontal overflow; all zones render.

Разница между файлами не показана из-за своего большого размера
+ 0 - 311
mocks/dashboard-control.html


Разница между файлами не показана из-за своего большого размера
+ 0 - 282
mocks/dashboard-current.html


Разница между файлами не показана из-за своего большого размера
+ 0 - 321
mocks/dashboard-primer.html


Разница между файлами не показана из-за своего большого размера
+ 0 - 304
mocks/dashboard-swiss.html


+ 12 - 177
src/pages/app.css

@@ -246,11 +246,6 @@
     align-items: start;
     align-items: start;
     margin: var(--spry-space-3) 0;
     margin: var(--spry-space-3) 0;
 }
 }
-
-/* Dashboard: the draft subtasks of a parked parent (plan 11 task D). */
-.waiting-drafts { margin: var(--spry-space-2) 0; padding-left: var(--spry-space-3); }
-.waiting-drafts li { margin: var(--spry-space-2) 0; }
-
 /* The status card's corner badge floats right of its heading. */
 /* The status card's corner badge floats right of its heading. */
 .status-corner { float: right; font-size: 0.9rem; }
 .status-corner { float: right; font-size: 0.9rem; }
 
 
@@ -498,134 +493,14 @@
 .plan-revision { margin: 0.5rem 0; }
 .plan-revision { margin: 0.5rem 0; }
 
 
 /* ==========================================================================
 /* ==========================================================================
-   Terminal
-   ========================================================================== */
-
-/* The terminal follows the page surface in both modes (a machine
-   surface, not a decorative dark panel): Spry tokens throughout. */
-.terminal {
-    background: var(--spry-bg-inset);
-    border: var(--spry-border-width) solid var(--spry-border);
-    border-radius: var(--spry-radius);
-    color: var(--spry-fg);
-    font-family: var(--spry-font-mono);
-    font-size: 0.85rem;
-    max-height: 40rem;
-    overflow-y: auto;
-    padding: var(--spry-space-3);
-}
-
-.term-entry {
-    background: var(--spry-bg-alt);
-    border: var(--spry-border-width) solid var(--spry-border);
-    border-radius: var(--spry-radius-sm);
-    margin: var(--spry-space-2) 0;
-    padding: var(--spry-space-2) var(--spry-space-3);
-}
-
-.term-entry .term-head {
-    display: flex;
-    align-items: baseline;
-    gap: var(--spry-space-2);
-    flex-wrap: wrap;
-    color: var(--spry-fg-muted);
-    font-size: 0.8rem;
-}
-
-.term-entry .term-command {
-    color: var(--spry-accent);
-    font-weight: 600;
-    white-space: pre-wrap;
-    word-break: break-word;
-}
-
-.term-entry pre {
-    border-radius: var(--spry-radius-sm);
-    margin: var(--spry-space-2) 0 0;
-    padding: var(--spry-space-2) var(--spry-space-3);
-    white-space: pre-wrap;
-    word-break: break-word;
-}
-
-.term-entry .term-output {
-    background: var(--spry-bg-inset);
-    color: var(--spry-fg);
-    cursor: pointer;
-}
-
-.term-entry .term-output.collapsed { max-height: 7.5rem; overflow: hidden; position: relative; }
-.term-entry .term-output.collapsed::after {
-    content: "… click to expand";
-    background: linear-gradient(transparent, var(--spry-bg-alt) 70%);
-    color: var(--spry-fg-muted);
-    display: block;
-    padding: var(--spry-space-3) var(--spry-space-3) var(--spry-space-2);
-    position: absolute;
-    bottom: 0;
-    left: 0;
-    right: 0;
-}
-
-.term-entry .term-input {
-    background: var(--spry-bg-alt);
-    border-left: 3px solid var(--spry-border);
-    color: var(--spry-fg-muted);
-}
-
-.term-entry .term-input.collapsed { max-height: 7.5rem; overflow: hidden; }
-
-.term-entry.type-query { border-color: var(--spry-accent); }
-.term-entry.type-query .term-command { color: var(--spry-accent); }
-
-.term-entry.type-comment .term-output,
-.term-entry.type-comment .term-command { color: var(--spry-fg-muted); font-style: italic; }
-.term-entry.type-block { border-color: var(--spry-warning); }
-.term-entry.type-block .term-output { color: var(--spry-warning); }
-.term-entry.type-user-comment { border-color: var(--spry-info); }
-.term-entry.type-user-comment .term-output,
-.term-entry.type-user-comment .term-command { color: var(--spry-info); }
-.term-entry.type-progress { border-color: var(--spry-success); }
-.term-entry.type-progress .term-output,
-.term-entry.type-progress .term-command { color: var(--spry-success); }
-.term-entry.type-review { border-color: var(--spry-warning); }
-.term-entry.type-review .term-output,
-.term-entry.type-review .term-command { color: var(--spry-warning); }
-.term-entry.type-plan { border-color: var(--spry-accent); }
-.term-entry.type-plan .term-output,
-.term-entry.type-plan .term-command { color: var(--spry-accent); }
-.term-entry.type-shell-reset { border-color: var(--spry-border); opacity: 0.85; }
-.term-entry.type-shell-reset .term-head { color: var(--spry-fg-muted); }
-.term-entry.type-failed { border-color: var(--spry-danger); }
-.term-entry.type-failed .term-output,
-.term-entry.type-failed .term-command { color: var(--spry-danger); }
-
-/* ==========================================================================
-   Text-selection comment popover
+   Text-selection quote button (the comment popover's only survivor)
    ========================================================================== */
    ========================================================================== */
 
 
-.quote-popover {
-    background: var(--spry-popover-bg);
-    border: var(--spry-border-width) solid var(--spry-popover-border);
-    border-radius: var(--spry-radius);
-    box-shadow: var(--spry-popover-shadow);
-    padding: var(--spry-space-3);
-    position: absolute;
-    width: min(26rem, calc(100vw - 3rem));
-    z-index: 40;
-}
-
-.quote-popover blockquote {
-    border-left: 3px solid var(--spry-accent);
-    color: var(--spry-fg-muted);
-    font-family: var(--spry-font-mono);
-    font-size: 0.8rem;
-    margin: 0 0 var(--spry-space-2);
-    max-height: 6rem;
-    overflow-y: auto;
-    padding: 0 var(--spry-space-2);
-    white-space: pre-wrap;
-}
-
+/* T11 sweep: the terminal surface (.terminal + the .term-entry
+   family, ~120 lines) and the .quote-popover pair are deleted — the
+   terminal page died with plan 17's event-log migration and app.js's
+   quote engine creates only this floating .quote-button + the dialog.
+   The event log's own row grammar lives in the Event log section. */
 .quote-button {
 .quote-button {
     background: var(--spry-accent);
     background: var(--spry-accent);
     border: none;
     border: none;
@@ -774,16 +649,6 @@ dialog#quote-comment-dialog blockquote {
     0%, 100% { opacity: 1; }
     0%, 100% { opacity: 1; }
     50% { opacity: 0.3; }
     50% { opacity: 0.3; }
 }
 }
-
-.log-task-link {
-    border: var(--spry-border-width) solid var(--spry-border);
-    border-radius: var(--spry-radius-pill);
-    color: var(--spry-accent);
-    padding: 0.2rem 0.7rem;
-    text-decoration: none;
-}
-.log-task-link:hover { background: var(--spry-bg-alt); }
-
 .log-shell {
 .log-shell {
     background: var(--spry-surface);
     background: var(--spry-surface);
     border: var(--spry-border-width) solid var(--spry-border);
     border: var(--spry-border-width) solid var(--spry-border);
@@ -1010,13 +875,6 @@ dialog#quote-comment-dialog blockquote {
 
 
 .event-result-body { margin-top: 0.3rem; }
 .event-result-body { margin-top: 0.3rem; }
 .result-ok { color: var(--spry-success); }
 .result-ok { color: var(--spry-success); }
-.result-failed { color: var(--spry-danger); }
-.result-stdout,
-.result-output {
-    color: var(--spry-fg);
-    font-family: var(--spry-font-mono);
-    font-size: 0.85rem;
-}
 .result-meta {
 .result-meta {
     color: var(--spry-fg-muted);
     color: var(--spry-fg-muted);
     font-family: var(--spry-font-mono);
     font-family: var(--spry-font-mono);
@@ -1050,10 +908,6 @@ dialog#quote-comment-dialog blockquote {
     white-space: pre-wrap;
     white-space: pre-wrap;
     word-break: break-word;
     word-break: break-word;
 }
 }
-.diff-del { background: var(--spry-danger-soft); color: var(--spry-danger); }
-.diff-add { background: var(--spry-success-soft); color: var(--spry-success); }
-.diff-ctx { color: var(--spry-fg-muted); }
-
 @media (max-width: 40rem) {
 @media (max-width: 40rem) {
     .log-header { align-items: flex-start; flex-direction: column; }
     .log-header { align-items: flex-start; flex-direction: column; }
 }
 }
@@ -1215,28 +1069,10 @@ dialog#quote-comment-dialog blockquote {
     .chat-bubble { max-width: 100%; }
     .chat-bubble { max-width: 100%; }
     .chat-composer-row { flex-direction: column; align-items: stretch; }
     .chat-composer-row { flex-direction: column; align-items: stretch; }
 }
 }
-
-/* ==========================================================================
-   SSH key panel
-   ========================================================================== */
-
-/* Agent celebrations (`saddle yeehaw`) — the earned 🤠 cards: a warm
-   warning-tinted festive panel (task page + event log shout rows). */
-.yeehaw-card {
-    background: var(--spry-warning-soft);
-    border: var(--spry-border-width) solid var(--spry-warning);
-    border-left: 4px solid var(--spry-warning);
-    border-radius: var(--spry-radius);
-    margin: var(--spry-space-3) 0;
-}
-.yeehaw-card h3 { color: var(--spry-warning); margin: 0 0 var(--spry-space-2); }
-.yeehaw-list { list-style: none; margin: 0; padding: 0; }
-.yeehaw-list li {
-    border-bottom: 1px dashed var(--spry-border);
-    margin: 0;
-    padding: var(--spry-space-2) 0;
-}
-.yeehaw-list li:last-child { border-bottom: none; }
+/* The yeehaw celebration row (T11: .yeehaw-card/.yeehaw-list deleted —
+   the earned-yeehaw card surface died with the entry stream; the live
+   celebration is the log's .yeehaw-message span, pinned by
+   test_task_log_js.py). */
 .yeehaw-message { font-weight: 600; margin-right: var(--spry-space-2); }
 .yeehaw-message { font-weight: 600; margin-right: var(--spry-space-2); }
 
 
 /* The saddle's SSH public key display: a quiet monospace panel. */
 /* The saddle's SSH public key display: a quiet monospace panel. */
@@ -1329,7 +1165,7 @@ dialog#quote-comment-dialog blockquote {
 /* Ambient zones (activity, notes, discoveries): reference material,
 /* Ambient zones (activity, notes, discoveries): reference material,
    deliberately flat — the rules below keep Spry's expander chrome
    deliberately flat — the rules below keep Spry's expander chrome
    from boxing them into card-weight sections that would compete with
    from boxing them into card-weight sections that would compete with
-   the attention column above (the mocks/ sampler review: collapsed
+   the attention column above (the goal-41 direction review: collapsed
    zones read as ruled rows, not cards). */
    zones read as ruled rows, not cards). */
 .dash-ambient > summary {
 .dash-ambient > summary {
     color: var(--spry-fg-muted);
     color: var(--spry-fg-muted);
@@ -1353,7 +1189,7 @@ dialog#quote-comment-dialog blockquote {
    (the t10views bundle asserts its absence), so the cap rules were
    (the t10views bundle asserts its absence), so the cap rules were
    dead selectors. */
    dead selectors. */
 
 
-/* The advisor hero's in-card heading copy (the samplers' composer
+/*/* The advisor hero's in-card heading copy (the direction review's composer
    shape): the description sits inside the composer card, above the
    shape): the description sits inside the composer card, above the
    textarea; the chats link sits with the action, left. Same
    textarea; the chats link sits with the action, left. Same
    specificity as the goal-27 rule above — this section is later in
    specificity as the goal-27 rule above — this section is later in
@@ -1598,7 +1434,6 @@ td .truncate.repo-url {
    the card's inset surface to read as one folding block. */
    the card's inset surface to read as one folding block. */
 .saddle-admin { padding-left: var(--spry-space-2); }
 .saddle-admin { padding-left: var(--spry-space-2); }
 
 
-
 /* ==========================================================================
 /* ==========================================================================
    Goal 41 T9 — library pages (project_detail / rules / discoveries / chats)
    Goal 41 T9 — library pages (project_detail / rules / discoveries / chats)
    No component additions: every surface adopts Spry primitives
    No component additions: every surface adopts Spry primitives

+ 17 - 10
src/pages/goal_detail.html

@@ -489,17 +489,24 @@
                         with your comment and submits a fresh completion report.</p>
                         with your comment and submits a fresh completion report.</p>
                     <button type="submit" class="btn-primary">Comment</button>
                     <button type="submit" class="btn-primary">Comment</button>
                 </form>
                 </form>
-                <dialog id="quote-comment-dialog">
-                    <h3>Comment on selected text</h3>
-                    <blockquote id="quote-text" class="term-comment-quote"></blockquote>
+                <dialog id="quote-comment-dialog" class="dialog-sm">
+                    <div class="dialog-titlebar">
+                        <p class="dialog-title">Comment on selected text</p>
+                        <button type="button" class="dialog-close" aria-label="Close"
+                                onclick="document.getElementById('quote-comment-dialog').close()">×</button>
+                    </div>
                     <form stm-action="goal.comment">
                     <form stm-action="goal.comment">
-                        <input type="hidden" name="quote">
-                        <input type="hidden" name="return_to">
-                        <textarea name="body" rows="4" placeholder="Your comment…"
-                                  style="width:100%; margin:0.5em 0;"></textarea>
-                        <button type="submit" class="btn-primary">Send comment</button>
-                        <button type="button"
-                                onclick="document.getElementById('quote-comment-dialog').close()">Cancel</button>
+                        <div class="dialog-body form-rows">
+                            <blockquote id="quote-text" class="term-comment-quote"></blockquote>
+                            <input type="hidden" name="quote">
+                            <input type="hidden" name="return_to">
+                            <textarea name="body" rows="4" placeholder="Your comment…"></textarea>
+                        </div>
+                        <div class="dialog-footer">
+                            <button type="button"
+                                    onclick="document.getElementById('quote-comment-dialog').close()">Cancel</button>
+                            <button type="submit" class="btn-primary">Send comment</button>
+                        </div>
                     </form>
                     </form>
                 </dialog>
                 </dialog>
             </section>
             </section>

+ 1 - 1
src/pages/goals.html

@@ -144,7 +144,7 @@
          required brief textarea (the goal's most important field) is
          required brief textarea (the goal's most important field) is
          visible the moment the dialog opens instead of hiding behind
          visible the moment the dialog opens instead of hiding behind
          a collapsed summary. Same fields, same stm-action. -->
          a collapsed summary. Same fields, same stm-action. -->
-    <dialog id="goal-create-dialog">
+    <dialog id="goal-create-dialog" class="dialog-sm">
         <div class="dialog-titlebar">
         <div class="dialog-titlebar">
             <p class="dialog-title">New goal</p>
             <p class="dialog-title">New goal</p>
             <button type="button" class="dialog-close" aria-label="Close"
             <button type="button" class="dialog-close" aria-label="Close"

+ 1 - 1
src/pages/rules.html

@@ -91,7 +91,7 @@
             <p stm-if="rules.rules.length == 0" class="text-muted">No rules yet.</p>
             <p stm-if="rules.rules.length == 0" class="text-muted">No rules yet.</p>
         </div>
         </div>
 
 
-        <dialog id="rule-create-dialog">
+        <dialog id="rule-create-dialog" class="dialog-sm">
             <div class="dialog-titlebar">
             <div class="dialog-titlebar">
                 <p class="dialog-title">New rule</p>
                 <p class="dialog-title">New rule</p>
                 <button type="button" class="dialog-close" aria-label="Close"
                 <button type="button" class="dialog-close" aria-label="Close"

+ 19 - 10
src/pages/task_detail.html

@@ -372,17 +372,26 @@
 
 
                         <!-- The highlight-to-comment popover (main.html) fills and opens
                         <!-- The highlight-to-comment popover (main.html) fills and opens
                              this dialog; it went missing in a template rework, which broke
                              this dialog; it went missing in a template rework, which broke
-                             quoting on RFC bodies. -->
-                        <dialog id="quote-comment-dialog">
-                            <h3>Comment on selected text</h3>
-                            <blockquote id="quote-text" class="term-comment-quote"></blockquote>
+                             quoting on RFC bodies. T11 normalised it onto the shared
+                             dialog shape (titlebar/body/footer). -->
+                        <dialog id="quote-comment-dialog" class="dialog-sm">
+                            <div class="dialog-titlebar">
+                                <p class="dialog-title">Comment on selected text</p>
+                                <button type="button" class="dialog-close" aria-label="Close"
+                                        onclick="document.getElementById('quote-comment-dialog').close()">×</button>
+                            </div>
                             <form stm-action="task.comment">
                             <form stm-action="task.comment">
-                                <input type="hidden" name="task_id" stm-attribute.value="task.id">
-                                <input type="hidden" name="quote">
-                                <input type="hidden" name="return_to">
-                                <textarea name="body" rows="4" placeholder="Your comment…" style="width:100%; margin:0.5em 0;"></textarea>
-                                <button type="submit" class="btn-primary">Send comment</button>
-                                <button type="button" onclick="document.getElementById('quote-comment-dialog').close()">Cancel</button>
+                                <div class="dialog-body form-rows">
+                                    <blockquote id="quote-text" class="term-comment-quote"></blockquote>
+                                    <input type="hidden" name="task_id" stm-attribute.value="task.id">
+                                    <input type="hidden" name="quote">
+                                    <input type="hidden" name="return_to">
+                                    <textarea name="body" rows="4" placeholder="Your comment…"></textarea>
+                                </div>
+                                <div class="dialog-footer">
+                                    <button type="button" onclick="document.getElementById('quote-comment-dialog').close()">Cancel</button>
+                                    <button type="submit" class="btn-primary">Send comment</button>
+                                </div>
                             </form>
                             </form>
                         </dialog>
                         </dialog>
                     </section>
                     </section>

+ 1 - 1
src/pages/tasks.html

@@ -443,7 +443,7 @@
          task" button (ux-notes quick win 4). The form itself is
          task" button (ux-notes quick win 4). The form itself is
          unchanged - same fields, same stm-action - and the shared
          unchanged - same fields, same stm-action - and the shared
          openAppDialog helper closes it on the action's notify. -->
          openAppDialog helper closes it on the action's notify. -->
-    <dialog id="task-create-dialog">
+    <dialog id="task-create-dialog" class="dialog-sm">
         <div class="dialog-titlebar">
         <div class="dialog-titlebar">
             <p class="dialog-title">New task</p>
             <p class="dialog-title">New task</p>
             <button type="button" class="dialog-close" aria-label="Close"
             <button type="button" class="dialog-close" aria-label="Close"

+ 1 - 1
src/pages/users.html

@@ -86,7 +86,7 @@
     <!-- The create dialog: same fields, same stm-action as the old
     <!-- The create dialog: same fields, same stm-action as the old
          expander form; the shared openAppDialog helper closes it on
          expander form; the shared openAppDialog helper closes it on
          the action's notify. -->
          the action's notify. -->
-    <dialog id="user-create-dialog">
+    <dialog id="user-create-dialog" class="dialog-sm">
         <div class="dialog-titlebar">
         <div class="dialog-titlebar">
             <p class="dialog-title">New user</p>
             <p class="dialog-title">New user</p>
             <button type="button" class="dialog-close" aria-label="Close"
             <button type="button" class="dialog-close" aria-label="Close"

Некоторые файлы не были показаны из-за большого количества измененных файлов