Prechádzať zdrojové kódy

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 deň pred
rodič
commit
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
 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
 
+> **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,
 colours and typography stay as they are. "Structure" here means page
 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.

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 311
mocks/dashboard-control.html


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 282
mocks/dashboard-current.html


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 321
mocks/dashboard-primer.html


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 304
mocks/dashboard-swiss.html


+ 12 - 177
src/pages/app.css

@@ -246,11 +246,6 @@
     align-items: start;
     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. */
 .status-corner { float: right; font-size: 0.9rem; }
 
@@ -498,134 +493,14 @@
 .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 {
     background: var(--spry-accent);
     border: none;
@@ -774,16 +649,6 @@ dialog#quote-comment-dialog blockquote {
     0%, 100% { opacity: 1; }
     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 {
     background: var(--spry-surface);
     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; }
 .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 {
     color: var(--spry-fg-muted);
     font-family: var(--spry-font-mono);
@@ -1050,10 +908,6 @@ dialog#quote-comment-dialog blockquote {
     white-space: pre-wrap;
     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) {
     .log-header { align-items: flex-start; flex-direction: column; }
 }
@@ -1215,28 +1069,10 @@ dialog#quote-comment-dialog blockquote {
     .chat-bubble { max-width: 100%; }
     .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); }
 
 /* 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,
    deliberately flat — the rules below keep Spry's expander chrome
    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). */
 .dash-ambient > summary {
     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
    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
    textarea; the chats link sits with the action, left. Same
    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. */
 .saddle-admin { padding-left: var(--spry-space-2); }
 
-
 /* ==========================================================================
    Goal 41 T9 — library pages (project_detail / rules / discoveries / chats)
    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>
                     <button type="submit" class="btn-primary">Comment</button>
                 </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">
-                        <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>
                 </dialog>
             </section>

+ 1 - 1
src/pages/goals.html

@@ -144,7 +144,7 @@
          required brief textarea (the goal's most important field) is
          visible the moment the dialog opens instead of hiding behind
          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">
             <p class="dialog-title">New goal</p>
             <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>
         </div>
 
-        <dialog id="rule-create-dialog">
+        <dialog id="rule-create-dialog" class="dialog-sm">
             <div class="dialog-titlebar">
                 <p class="dialog-title">New rule</p>
                 <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
                              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">
-                                <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>
                         </dialog>
                     </section>

+ 1 - 1
src/pages/tasks.html

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

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov