Revert the dashboard-level gutter from the previous commit - it addressed
the wrong element.
The approved mockup gave the right pane a hairline divider and a 24px
left inset (.pane-div). The plan's renderRightPane only ever created a
plain .wd-pane, so neither reached the implementation. The table cells
are deliberately zero-padded on the left so the concept column aligns
with the eyebrow above it, which only reads correctly when the pane
supplies the inset - without it the table ran flush against the grid gap.
Marked with an explicit class rather than :nth-child, since an error box
can occupy that grid slot. Collapsed to one column, the divider moves to
the top edge.
Lifting the host's readable-line-width cap also removed the centring
that was supplying the note's side margins, so content ran flush to the
pane edge. Add margin-inline:auto and a 24px padding-inline on the
dashboard itself rather than reaching into Obsidian's layout: centred
when the pane exceeds the 1600px cap, and a fixed inset at every width.
Measured chain from live preview showed the cap on .cm-content at 700px,
while .cm-sizer - the element closest() matched first - is already
uncapped at 1680px. The class-name shortcut therefore locked onto the
wrong element and returned before the fallback walk could find the real
one, so the cap survived and the container query correctly collapsed the
grid to one column.
widenHost now always measures computed max-width outward from the
dashboard's parent. The decision is extracted as firstCappedIndex and
tested against the real measured chains for both live preview and
reading view, so a class-name assumption cannot silently break it again.
Adds .cm-content to the CSS fallback.
The CSS-only fix lost a specificity fight: Obsidian qualifies the sizer
with its view class, outranking a bare .markdown-preview-sizer:has(...)
declaration with no !important. The cap stayed, the container query
correctly saw a ~700px container, and the grid collapsed to one column.
widenHost() now lifts the cap inline at render time, which cannot lose on
specificity and does not depend on Obsidian's class names staying stable.
The CSS rules remain as belt-and-braces, now with the view-class variants
and !important.
Obsidian writes CRLF on Windows, so git reported untouched files as
modified. That matters because `git diff HEAD` is the review surface
after a headless ingest and is useless if everything shows as changed.
`* -text` stores bytes as-is.
Obsidian caps note content at --file-line-width (~700px) with readable
line length on, which squeezed the two-pane grid into a column. Widen
only the sizer that contains a dashboard via :has(), so every other note
keeps its readable width, and cap the dashboard itself at 1600px.
Also swap the collapse breakpoint to a container query. A media query
measures the window, so a wide window with the dashboard in a narrow
split pane kept two cramped columns; the container query responds to the
pane. The media query stays as a floor for a narrow window.
- I4: added a fourth Required Page Template for wiki/script-coverage.md
spelling out the exact positional shape parseCoverageTable requires
(metadata line syntax, column order, station spelling, lowercase all/yes) —
Workflow D described the fields but never the shape, so a compliant write
could still fail every parse. Also spelled out the seven station names in
Workflow D step 3 and resolved the step 4 (pinned rows are protected) vs
step 5 (stale rows are removed) ambiguity for a pinned row whose concept
page is gone.
- I5: added Workflow A step 9, pointing any ingest that touches
wiki/concepts/ at Workflow D before reporting — previously that trigger
lived only inside Workflow D's own list, so an ingest run top to bottom
never synced coverage and the right pane went stale silently.
- log.md: added "sync" to the intent docstring's valid values (Workflow D
already required it) and corrected the 2026-07-28 entry's attribution —
the concept grouping lives in index.md, not in script-coverage.md itself.
- I2: .wd-row now wraps and .wd-output takes the full row width, so streamed
output no longer gets crushed beside the button on the filename's line.
- I3: split .webinar-dash into token scope + a new .wd-bar toolbar above a
.wd-grid two-pane layout. Extracted renderAll (called from onload) which
renders a global "Sync coverage" button, generalised runIngest's subprocess
handling into a shared runClaude keyed by run id, and added spawnGate to
preflight-disable both the sync and per-file ingest buttons when claude
can't be spawned (no desktop Obsidian / no child_process). A successful
ingest or sync now re-renders the dashboard instead of requiring reopen.
- I6: the right pane now shows the tracked script path and last-synced date
from the coverage file's metadata, and flags when cfg.script disagrees
with it.
- Minors: documented the unescaped-pipe split regex, added role="img" to the
coverage meter alongside its aria-label.
spawn(..., { shell: true }) made Node concatenate argv without quoting;
cmd.exe re-tokenized it and the ingest prompt arrived as a bare "ingest"
with the filename silently discarded, while the run still reported
success. claude.exe is a real executable here, so no shell is needed:
drop shell: true and pass argv directly. isSafeFilename is unchanged
(still correct defence in depth) but its comment now reflects that the
spawn path carries no injection surface today.
Caret and control characters added to the filename guard, file-keyed in-flight
registry replacing the row-scoped one, try/catch around spawn, single finish()
exit path, UTF-8 chunk decoding, bounded output buffer.
index.md groups integration-dead-ends and leave-less-room-for-imagination
under Machine side, and both are absent from the script. The claim that every
absent concept is human-side or strategy-side was wrong: it is 8 of 10.
Corrections made during execution as reviews surfaced defects in the plan
itself: duplicate raw-path claim handling and its one-pass orphaned filter,
unescaped-pipe table split, RAW_EXTENSIONS widening, dark accent tracking the
installed theme, the bare node --test command, and test counts.
- readPipeline no longer filters raw/sources to .md only; CLAUDE.md
documents txt and pdf as valid raw source types, and hiding them from
the queue silently was the same invisibility bug the dashboard exists
to remove.
- Dark-mode accent now tracks the installed tesanti theme (same signal
red, no base-color lift) instead of contradicting it; status colors
(ok/warn/danger) keep their lifted dark values for AA contrast on
small text, per design-system separation of status tokens from the
brand accent.