Commit Graph
802 Commits
Author SHA1 Message Date
amosblomqvist 0f2519a349 ui: round five — no band in cards, the rail narrows again
Owner verdict on the accent step: way too light - in-card thinking
drops the band entirely (rail only, identical heights). The rail
narrows again: 14px gap inside the 3ch inset (rail calc(3ch-14px),
about 8px) - the wide rail was distracting. Total inset still 3ch,
chevron law intact.
2026-10-08 18:45:49 +03:00
amosblomqvist d30ad42d9c ui: round four — thinner rail, tighter band, the accent step, early wrap
Owner round four on the user rows: (1) the rail thins - the gap inside
the 3ch inset grows 5px -> 8px, so the rail is calc(3ch-8px); total
inset still 3ch, chevron law intact. (2) The band padding drops again,
5px -> 4px (py-1). (3) The in-card thinking band steps to bg-accent -
the same ONE-STEP lift the chat band gets (background->card on the page,
card->accent on the card); card-color on card-color was invisible, the
accent gives the card rows a visible band with identical heights. (4)
pr-4 on the band rows - the expanded text wraps EARLY instead of hugging
the band's clear right edge.
2026-10-08 18:38:07 +03:00
amosblomqvist 497b29804c ui: round three — the tight band, one geometry everywhere
Owner round three on the user rows: (1) the band padding halves,
py-2.5 -> py-[5px] — subtle, not tall; same for the messages. (2) The
in-card thinking joins the band: identical styling and heights for
collapsed thinking in the chat and inside question cards (card-on-card
is quiet by design; revert if it reads too light). (3) The left gap
MATCHES the new 5px padding, so the rail fills the rest of the 3ch
inset - border-l-[calc(3ch-5px)] pl-[5px]: a thick muted rail, total
inset still 3ch (the chevron-alignment law survives). The banded prop
dies - one geometry, cards included.
2026-10-08 18:32:21 +03:00
amosblomqvist 3691e07118 ui: the document, all-in — card-color bands and the 6px rail
Owner round two on user-vs-agent clarity (the chat-comeback was tried
and set aside): we go all in on documents. No chat sides. User messages
and every transcript user-thinking row are full-width CARD-COLOR BANDS
- the band is the authorship mark against the agent's transparent
prose. No rounded corners, no borders besides the rail.

The rail keeps its muted color but is now 6px wide (was 2px): the
artificially wide 3ch inset (the chevron-alignment law with the agent's
flush rows) is re-split - rail eats the air, the rail-to-text gap drops
from 3ch-2px to a natural 3ch-6px, and the total inset stays 3ch so the
chevrons still align. The 'You' label stays on messages - one register
with the thinking labels.

In-card thinking (inside answered cards) takes the rail but NO band:
card-color on card-color would be invisible; geometry alone carries it.
Transcript rows arrive expanded (clearly sent) - the reading lift now
opens on arrival too; the in-card row stays collapsed. Cmd+T still
rules all rows.

Also this round: the answered-card last-child rhythm law
([&>*:last-child]:mb-0) - on ask cards the thinking row's my-3 stacked
with the card's p-4 into a 28px void below (12px above); the card's own
padding is the bottom rhythm when a row ends the card.
2026-10-08 18:17:11 +03:00
amosblomqvist 8cb5e14691 desktop: the payload fixed — 7.4GB→144MB, dev keys out, boot-smoke gate
- desktop:dist cleans dist/ + .next/standalone before next build: the
  trace sweep was shipping the PREVIOUS build's DMG inside every new one
  (standalone/dist = 8.0GB inside the app; measured 7.4GB DMG → 144MB).
- the same sweep shipped the repo-root auth.json (dev BYOK keys,
  byte-identical) in every DMG ever built — excluded in the trace and
  REFUSED by a new build-server.mjs guard (.env* belt-and-braces).
- WARNING (next.config.ts): dir-style trace excludes (dist/** etc.,
  "./"-anchored or not) strip server-external packages to bare
  package.jsons — pi-ai/pi-agent-core lost their dist/, the packaged
  server 500'd on ERR_MODULE_NOT_FOUND. Only the three verified-safe
  entries stay.
- new scripts/smoke-server.mjs gates desktop:dist between payload
  curation and packaging: boots the standalone exactly as the Electron
  shell does (temp LH_DATA_DIR, empty auth) and requires 200s on / +
  graph/models/settings. Caught both defects above on its first day.
- entitlements: allow-unsigned-executable-memory dropped (2019-era
  relic per current electron-builder docs; weakens hardened runtime).
- pi-ai + pi-agent-core pinned exact 1.0.2 — persisted chat records
  carry their wire shapes; post-release a bump is a migration event.

Gates: typecheck, lint, 1459/1459 tests, full desktop:dist + smoke PASS.
2026-10-08 17:35:15 +03:00
amosblomqvist d5c4c3bf9f ui: the chat comeback — side-based authorship, restored quotes, tucked whys
Blockquotes: the callout override had displaced Streamdown's own styled
blockquote component, so plain quotes rendered as bare unstyled text
since that commit. The pass-through branch now restores Streamdown's
verbatim classes (border-l-4, muted italic).

Explanations tucked for ALL questions (owner): the prediction's Why /
Hide-why toggle is universal — every quiz answered card sits its
explanation behind the same collapsed button. Same pattern, one law.

The thinking row's rhythm: my-1 -> my-3. The 4px margin hugged the
options on non-graded answered cards (owner report); 12px matches the
card's mt-2/mt-3 spacing grammar everywhere the row renders.

Side-based authorship (owner round, 2026-10-07): the document shape
blurred user into agent — three left-rail idioms (user text, user
thinking, quoted prose) collided once quotes rendered. User rows go
RIGHT: 'You' messages and practice thinking cards hug the right edge at
85% width with the rail mirrored (border-r), text still reading
left-to-right. In-card thinking (inside the answered card, the agent's
context) stays left. Transcript thinking rows arrive EXPANDED — the
thinking was clearly sent. Cmd+T (toggle-user-thinking, chat scope,
cheat-sheet row) sets every user-thinking row to one state via a module
store: explicit sets stomp individual toggles, late mounts honor the
last set (the state initializer reads the store, so static render
reflects it too).
2026-10-08 02:29:05 +03:00
amosblomqvist cdf9305eea prompts: no size ceilings, the detour keeps the pace, the probe enters at the top
Limits out (owner rulings): the 'Typically 5-15 nodes' cap and the
'keep the map small' pressure are dead — a plan is as elaborate as the
subject needs; what keeps it readable is correct structure (unconditional
truths as the hanging points), not compression. Labels stay short; size
is free. The method section now carries the goal reiteration: thorough,
deep, real understanding — sessions run as long as the understanding
needs, plans as elaborate as the subject requires; no schedule, nothing
to rush. (Code was already limit-free: no maxTurns, no parser caps.)

The adaptation + detour law, merged into one section ('The map moves
with him'): when the ground shifts mid-lesson — a missing foundation, a
deeper confusion — never rush the repair; no wall of text that 'covers'
the ground to get back on plan. Update the map if the structure changed,
then build the missing ground the only way anything is built here: one
reasoning step at a time, verified, at the session's one and only pace.
Detours are the teaching, not interruptions to get through.

The probe enters at the top (owner-observed failure: eigenvectors, then
high-school chain rule, then partials — strands opened at textbook
floors). New entry-point rules: open near the hardest thing the goal
needs and let a miss walk you down; strands inform each other (a strand
crushed at height says neighbors start high); the verdict + confidence +
thinking field TOGETHER set the next question's difficulty — confident
correct jumps sharply, hesitant correct steps gently. Efficiency is
resolution: never descend into already-bracketed territory; the
questions you save buy the specific, nuanced ones that locate the edge.
2026-10-08 01:13:05 +03:00
amosblomqvist 5d9cebc3d5 prose: the key-idea callout — GitHub alerts, one sharp white box; tables hug
The one emphasis device for load-bearing ideas: GitHub-style alert
callouts. The prompt teaches only > [!IMPORTANT] (one device, one
meaning — an unconditional truth laid down, a payoff landed, a hinge
definition) with the restraint law: everything else stays in prose, if
everything is boxed nothing is. The renderer accepts all five GitHub
markers and boxes them identically — a stray [!NOTE] still meant 'this
deserves emphasis', and a marker line must never leak as text.

Rendering (owner rulings): SHARP corners, white stroke, no background,
no label, no icon — just the outline — and the box hugs its text
(width: fit-content capped at the column) instead of stretching
full-width into empty space. Same grammar as the plan blocks: this is
structure, not chrome.

Seam: the Streamdown blockquote override in md-blocks. parseAlertMarker
is pure and exported — Streamdown wraps blocks in its own styled
components, so detection reads the hast node's tagName, and a standalone
marker drops its <br> hard-break artifact along with the empty
paragraph. Plain blockquotes pass through untouched.

Rider (owner, same round): tables hug their contents — width:
max-content on the data-streamdown table (width:auto would still
stretch, the classic table trap); wide ones keep the md-blocks
overflow-x scroller. Graceful in exports: a callout is still a
blockquote there.

6 render pins: the box mounts with white stroke + fit-content + no
rounding, all five markers box, standalone-marker drops the empty
paragraph, multi-paragraph structure kept, plain quotes untouched.
2026-10-08 00:27:30 +03:00
amosblomqvist c34cc029ae prompts: the fact-check bar, the thinking fields, adaptation, LaTeX everywhere
Accuracy (shared, one law across every agent): the target shifts from
surface facts to understanding itself — 'the deeper risk isn't a wrong
name or date, it's a wrong understanding: an interpretation of an idea
that seems solid and isn't.' The bar is the doubt itself: checks are
cheap, and weighing whether a doubt is worth checking is how errors slip
through. Thinking stays the agent's own.

Research (teacher 40-jobs): floor framing — verify your understanding
before it becomes his; dispatch before the fact crosses your lips, teach
around the gap until the brief lands. Planning (10-planning): planning is
the natural moment for verification; the map is a hypothesis and the
probe only its first sketch — rewriting is a normal, common event (the
session adapts to what teaching reveals); and plan_init IS the
presentation — never draw the plan again in prose (the double-plan bug).

New shared segment 16-mental-model.md: the thinking fields are the
session's primary data — read them in full, measure what they reveal,
adapt everything on the model they build. Composes into teacher, prober,
and practice (shared-fragments list updated).

Practice: the rarely-needed framing is dead — fact checks get their own
law (a wrong model solution grades the learner against a lie; pose once
the brief lands), files and map readers become plain side tools.

LaTeX: the question-tools craft list gains the point-of-use bullet
('LaTeX everywhere — plain-text math in a question is a craft failure'),
the formatting law names EVERY field, and the practice problem statement
line carries it too.

Prompt pins updated: felt-doubt bar + mental-model doctrine in both
teacher and practice compositions, no-double-plan rule, rewrite-is-
normal law, rarely-needed framing asserted gone.
2026-10-07 23:56:21 +03:00
amosblomqvist 8dfe621748 researcher: PDFs from the web — the pdf-reader's text tools in the loop (design B)
web_fetch_pdf downloads (25MB cap, probe-validated) into a per-job temp
dir and returns the triage summary — page count, size class (the pdf
doctrine's <=10/<=40 thresholds), flagged math/image-heavy pages. The
researcher's toolset then gains pdf_info / pdf_extract / pdf_search over
that dir, scoped by a resolve-and-startswith wall: the researcher's
filesystem ends at the temp dir. Render is deliberately absent — the
researcher slot has no vision; visual reading stays the pdf job's job.

web_fetch: a PDF URL now returns a POINTER to web_fetch_pdf instead of
dead-ending, and every output carries 'Source: <final url after
redirects> (HTTP <status>)' — the 'ill-formed request' lesson: a
200-served error body must be distinguishable from a real page.

The runner owns the temp lifecycle (mkdtemp before, recursive cleanup in
the job's finally — never enters the record) and web_fetch_pdf urls ride
the sources ledger. Doctrine step 5 added: fetch, triage, extract only
the pages that matter, cite as (p. N).

Tests build a REAL minimal PDF programmatically (correct xref offsets —
pdfjs parses it like any download): zero network, real parse.
2026-10-07 22:36:11 +03:00
amosblomqvist d450689a8b fix(web_fetch): the protocol gate never accepted a URL — the colon bug
URL.protocol carries a trailing colon ("https:", WHATWG); the colon-less
comparison (a direct port from the py harness, where urlparse().scheme
has none) rejected EVERY valid URL ever fetched — the tool never fetched
a page in its life. Owner-reported via OpenRouter logs: 8 valid URLs in,
8 'invalid URL' results out.

The new pin stubs globalThis.fetch so the happy path runs hermetically —
a valid https URL must REACH the fetch stage (and ftp:// still rejected).

dist rebuilt (desktop:dist) — bundle verified: "http:"!==t.protocol
2026-10-07 22:08:29 +03:00
amosblomqvist 34f498b128 artifacts: the height ratchet fixed, invert switch persisted + restyled (release polish 7.2)
- bridge measures the BODY (scrollHeight + rect + margins) instead of the
  viewport-clamped documentElement — documentElement.scrollHeight latches
  at the parent's own height, so demos could grow but never shrink on
  window resize (owner-reported); smoke: 1036 → 2386 → 1036 px
- invert now PERSISTS as details.ui.inverted on the steer record, written
  by the silent artifact-ui route (dismissal pattern: no wake, no SSE) and
  seeded through the projection (SubagentArtifactData.ui); survives
  navigation + reload, never enters teacher context or exports
- invert restyled as the house Toggle switch (settings controls) on both
  cards; demo header is the caption grammar: 'INTERACTIVE DEMO — title'
  left (title normal-case, same size/tracking), switch + download far
  right; figure header: label left, switch/download/open far right

1439 tests green (route + pure-seam pins added); live smoke passed
2026-10-07 19:13:24 +03:00
amosblomqvist 4070cc7926 artifacts: demo scrollbar death, figure natural sizing + fullscreen, downloads, invert toggles (release polish 7)
- demo iframe: scrolling=no (the bridge auto-sizes; a scrollable iframe
  trapped the wheel) + content-box so the bridge height is the viewport
- figures: svgNaturalWidth hug + center (w-fit max-w-full mx-auto),
  full-width fallback for viewBox-only svgs
- fullscreen: MermaidOverlay figure mode (raw svg, white stage, invert
  carried); openFigure context; figureFocus rides the mermaid Esc layer
- downloads: figure.svg + <slug>.html (raw pre-bridge), shared download.ts
- invert: manual ContrastIcon toggle on both cards — invert(1)
  hue-rotate(180deg) on the white-bearing element, default off, no
  detection, no theme wiring (owner rulings 2026-10-07)

1433 tests green; live smoke passed (hug/center measured, filters verified,
downloads fired, Esc closed; wheel-chaining structurally sound)
2026-10-07 13:45:03 +03:00
amosblomqvist a763b9fc5c graph: the sliders re-centered on the new defaults (release polish 6.2)
The owner speaks in display values: "labels until zoom" shows ratio/0.5,
so the old default displayed 8× — and "default one" means display 1× =
ratio 0.5, one stop tighter than the round-1 value. Both ladders are
re-centered so the new defaults sit dead-center again (stop #5):

  labelZoom: [0.125, 0.175, 0.25, 0.35, 0.45, 0.5, 0.75, 1, 1.5, 2, 3]
    — default 0.5 (displays 1×); finer steps below the default, the old
    familiar run above; old stored 4/24 snap to 3.
  blur: [10, 15, 20, 25, 30, 40, 50, 60, 80, 100, 120]
    — default 40 dead-center, min 10 per the owner's suggestion, old
    ceiling kept; old 4/5/7.5 snap to the new floor.

The dead-center ladder law is restored with no exceptions; migration
pins updated (blur 8.1 → 10, labelZoom 0.5 now a member).

1428 tests, tsc, lint, build green.
2026-10-07 12:55:11 +03:00
amosblomqvist 22ab520b5c graph: new default render tuning — blur 40, labels until zoom (release polish 6)
Two defaults moved by owner ruling (2026-10-07): the region blur
doubles 20 → 40 (softer, wider cluster tints) and labelZoom drops 4 → 1
(node labels hidden at the overview, shown once the camera reaches fit
zoom or closer). Stored values are untouched — only the absent-field
fallbacks moved. The dead-center ladder law gains two owner-moved
exceptions (labelZoom index 1, regions.blur index 7); every other
default stays stop #5.

1428 tests, tsc, lint, build green.
2026-10-07 12:46:28 +03:00
amosblomqvist bab0d3f94d copy: the thinking-field placeholders, the brief round (release polish 5)
Four placeholders rewritten per the owner's set (2026-10-07): the
thinking fields are an invitation to stream-of-consciousness transfer,
and everything else is implied by the field's existence — no "the
teacher reads all of this", no mechanism, no em dashes, nothing that
wraps. The reading field's placeholder no longer breaks to a second
line.

  quiz:     "Why this answer? Whatever comes to mind."
  reading:  "Whatever comes to mind as you read."
  practice: "Work here, think out loud."
  pause:    "Ask anything, or press Enter for the next one."

The answer-field placeholders ("Type your answer…") stay: brief and
functional already (owner ruling). Quiz placeholder pin updated.

1428 tests, tsc, lint, build green.
2026-10-07 11:03:54 +03:00
amosblomqvist f979e35f57 plan: the hug calibrated — measured rates, 16px symmetric, white outlines
The first cut's flat 6.8px/char table was calibrated for a generic 12px
sans; the plan titles actually render in Source Sans 3 at 11px — much
narrower — so every box carried ~10px of invisible slack per side (the
owner's "a lot of empty space left and right of the text"). The class
rates are now least-squares-fitted against getComputedTextLength
measurements of the live plan titles (narrow 3.1, wide 9.2, caps+digits
6.0, default 5.4): residuals land within ±4px per line, and PAD_X stays
16 — equal to the vertical (44−11)/2. Smoke-verified: per-side padding
now reads 14.4–19.1px across all 12 nodes (was 22.4–28.6, 10/12 over
25). And per the owner's follow-up: the in-transcript boxes keep their
WHITE outlines (gray card fill good, box edges stay white).

1428 tests, tsc, lint, build green.
2026-10-07 09:40:33 +03:00
amosblomqvist fa61251cfb plan: the hug — text-first boxes, centered titles, dock-colored transcript
The plan skin drops its fixed 160px slabs: every box now sizes to its
wrapped text (px-per-char estimate + symmetric 12px padding, floored at
96px), the wrap limit rises 20→28 chars — the early-wrap fix, boxes
grow so lines stop breaking with room to spare — and every title line
anchors to its box midline (textAnchor middle, was a x=12 left inset).
The in-transcript snapshot block speaks the dock language now: boxes
fill-card stroke-border like the composer shells, text fill-foreground,
retiring the white-on-transparent mono look. The popup and the margin
diagram keep their status colors. Mermaid proper is untouched (owner:
it's fine as is).

Character-count estimation, not canvas measurement: the layout path
stays deterministic, synchronous, DOM-free — SSR and exact render pins
keep working. Verified live on the Relational-databases plan (12 nodes,
widths 174–215 tracking the longest line, no overflow, glow intact).

1428 tests, tsc, lint, build green.
2026-10-07 09:25:14 +03:00
amosblomqvist 8aa1e23cba keyboard: home's Tab targets its OWN composer — the smoke catch (keyboard 3 follow-up)
The smoke found it: Tab from body on home preventDefault'd but never
focused — home does not render the shared Composer component; its
composer is the bespoke topic textarea, and the selector matched
nothing. The chain is now topicInputRef itself.

Also verified live this round: the one-Esc law on plan/chains/settings
(exactly one action, nothing stacked), Enter-submits from the Other
input (a real session filed the custom answer immediately), and
Shift+Tab staying the thinking-level cycle everywhere.

1427 tests, tsc, lint, build green.
2026-10-07 00:32:05 +03:00
amosblomqvist e523bea670 keyboard: the one-Esc law — a surfaced layer claims at the document level (keyboard 4 of 4)
Owner ruling (keyboard round 2026-10-06): Esc (or any key) does ONE
action, on the topmost active layer. The plan-open-while-dictating
double-act was the symptom: the plan popup listened at WINDOW level
with no claim, and same-node window listeners all fire — the global
chain (registered first) cancelled the dictation while the plan's own
listener closed the plan. One Esc, two actions.

The law (now the house pattern, documented at the chain):
- field-level handlers claim at their element (focus in a field: the
  field's action) — unchanged, already correct
- every SURFACED layer claims at the DOCUMENT level with preventDefault
  + stopPropagation — document fires before the window chain, so the
  chain stays silent under any surface
- the window chain is the bottom-up fallback: dictation (a live capture,
  NOT a surface — it survives the Esc that closes a plan or overlay),
  then abort (escAbortTarget layers its own targets)

Converted to the claim: plan popup, session overlay, chain table,
settings dialog, setup dialog, the delete/abort dialogs (window-level
stopPropagation was never enough — same-node listeners all fire), and
the stats popover. Popovers (popover-dismiss), menus, model picker, and
the sidebar context menu already claimed correctly.

1427 tests, tsc, lint, build green.
2026-10-07 00:08:49 +03:00
amosblomqvist 94470815ab keyboard: Tab cycles the surface's text fields, forward-only (keyboard 3 of 4)
Plain Tab now navigates instead of being pure dead weight: the global
handler dispatches lh:kbd-cycle-fields and each composer-bearing
surface answers with its own chain, gathered from live DOM at event
time (the docks mount and unmount — refs would go stale):

- chat: reading-thinking → the active dock's field → the composer,
  visual order, wrap; Tab from anywhere else focuses the first live
  field (navigate in)
- new-session + home: the composer is the whole chain — Tab navigates
  into it

Shift+Tab is untouched (the thinking-level cycle keeps it — owner
ruling); the quiz dock's rows↔thinking Tab claim stops propagation at
document level, so a claimed Tab never reaches the dispatch — no
double act.

1427 tests, tsc, lint, build green.
2026-10-07 00:04:03 +03:00
amosblomqvist 9007e6eb7a keyboard: Enter submits from the custom answer; Enter opens the waiting check (keyboard 1-2 of 4)
Slice 1 — the ask's custom-answer field: Enter submits directly. A
single-line input with no newline to protect, and typing has already
selected the row — every Enter (shift or not) rides the same submit()
seam as the button, dictation finish-first included. No more the
Esc-then-Enter dance to file a custom answer.

Slice 2 — Enter opens the waiting check (owner, keyboard round
2026-10-06): while a question waits with the dock CLOSED (behind its
reveal invitation, or collapsed by scroll) Enter is never dead. Any
live dictation finishes FIRST — the flush lands in the reading-thinking
field, the live claim, per the nothing-gets-lost law — then the check
reveals/expands. Two seams: the reading-thinking field's own Enter
(plain or ⌘/Ctrl; Shift+Enter keeps the newline convention — the dump
convention survives for deliberate breaks, and Enter stays a newline
once the check is OPEN), and a document-level listener for plain-focus
Enter — gated to skip typing surfaces and focused buttons, which own
their own Enter.

1427 tests, tsc, lint, build green.
2026-10-07 00:00:57 +03:00
amosblomqvist 95ba9c8272 compaction: not viewable — the dividers stay, inert (release polish 4)
Owner ruling (release polish round, 2026-10-06): the compaction
summary dialog is gone. The content is model-facing plumbing — anyone
who wants to inspect it reads their provider logs. Both dividers
survive as INERT markers: the moment divider's 'Context compacted'
note and the cut divider's 'HISTORY COMPACTED · 152k tok' seam chip
render as plain text; no button, no dialog, no state. The marker parts
and the server fold are untouched — this is a pure render-side
removal.

Pins: dividers render without <button> or cursor affordances, and the
marker's summary text never reaches the DOM. 1427 tests, tsc, lint,
build green.
2026-10-06 14:18:05 +03:00
amosblomqvist 0b2e4e03fb sessions: export as markdown — the sidebar right-click, the route, the practice join (release polish 3)
Owner round (2026-10-06): markdown export of sessions, actioned from
the sidebar context menu — "Export as Markdown…" sits with the
management actions, above Delete (the destructive action stays last).

- GET /api/chats/:id/markdown (404 unknown): the whole session as one
  attachment — text/markdown; charset=utf-8, filename from the slugified
  title ("relational-databases-joins-normal-forms.md", non-ascii titles
  fall back to "session.md"). Both session kinds export.
- The client is a same-origin anchor download; the route's
  Content-Disposition names the file.
- sessionMarkdown's user-facing debut — minted ANCHORLESS (message-id
  anchors are extraction provenance, not learner-facing). The engine
  joins the core barrel (pure, browser-safe) for its first consumer.

The practice join (owner-approved mid-round): practice tool results are
bare acks — statement, outcome, verdict, and the model solution all
live in the assistant's call arguments. The engine now folds them the
same way the view seam folds cards: Problem blocks render the pose's
statement (solution never leaks at pose time); Concluded blocks render
the outcome as prose ("Solved with hints.") + verdict, then the
pose-committed model solution as a blockquoted reveal via
problemInputForConclude (clean-poses-only, its refusal law). A
dismissed problem concludes as "The learner skipped this problem." —
no reveal. Errored poses render nothing (the failed-tool law).

1427 tests (route pins: headers, slug, anchorless body, 404; engine
pins: pose-time statement, the reveal, the dismissal refusal, the
errored-pose silence); tsc, lint, build green.
2026-10-06 10:23:16 +03:00
amosblomqvist 8a7b6b30d9 minimal home: the buttons carve out of the drag band; own-line summary (release polish 2.3)
Owner report (Electron): the Chains and Import-notes buttons were not
clickable — clicking dragged the window. Root cause: the chrome column
lacked lh-no-drag. The drag mask resolves by DOM order, and the graph
chrome's column carries the carve exactly for this reason (the
2026-09-17 mask law). The minimal home's column now carries the same
class on the same shape — byte-for-byte the graph chrome's law — and
the pointer-events juggling is gone (it was browser-thinking; Electron
needs the region carve, not the pointer filter).

Summary refinement (owner, same round): every count on its OWN line —
no dot separators; the two blocks (propositions/chains, teaching/
practice sessions) part with one empty line (a dedicated h-4 spacer);
imported notes render as a COUNT ("4 imported notes"), never the file
list. KnowledgeSummary's contract changes sources: string[] →
importedNotes: number.

1419 tests (own-line pins: no " · " anywhere, h-4 parting, count-not-
paths, singulars); tsc, lint, build green.
2026-10-06 10:04:26 +03:00
amosblomqvist 0cc108615b graph: the MINIMAL home — the no-graph home, take two (release polish 2.2)
The chains-as-home experiment is retired (owner, 2026-10-06): dragging
the table out of its overlay home caused every mess we saw — checkboxes
by default, the bar/pill collision, the clearance band. The eventual
plan is the DATA VIEW as home; until that is designed and built, the
rendering-off home is deliberately minimal:

- The SAME composer blob home already floats — teach/practice switcher
  and pick pill untouched.
- The graph chrome's survivors in the graph view's OWN places: the
  top-right column — actions row (Chains + Import, the same 36px band
  pills) with the cost pill BELOW it. Import always renders (the
  entry-point law). No node search, no render gear (canvas things).
- A quiet centered summary (KnowledgeSummary, a pure body — pinned by
  the render suite): propositions · chains, teaching · practice session
  counts, the imported-sources list as truncated muted rows; the empty
  knowledge base says so and names the import entry point. Biased up
  off the composer (pb-40), pointer-events-none.
- NO recent sessions (the sidebar owns that, owner ruling).

The chain table returns to overlay-only duty: ChainTableBody's
onClose-optional and topBarExtra experiment hooks are reverted, and
components/chains-home.tsx is deleted. MinimalHome renders the STANDARD
ChainTableOverlay behind its Chains button — the pick flow mirrors
GraphView's contract verbatim (chainSelectOpen raises the overlay in
select mode; Done reports the lifted pick set down).

Smoke-verified live, twice: the summary shows real counts (262 props ·
160 chains, 195 teaching · 17 practice, 4 source paths); overlay
open/close via button + X; pick flow — cluster check → bar "23 nodes →
17 chains · Clear · Done" → uncheck 2 props → Done → chip "21
propositions selected"; practice session starts from the composer;
settings restore returns the canvas home. (First run's "select mode
missing" was a tester artifact: the bar is selection-gated and prop
checkboxes render only when expanded — both confirmed working.)

1417 tests (new KnowledgeSummary pins); tsc, lint, build green.
2026-10-06 09:47:26 +03:00
amosblomqvist 83103062f5 graph: import + cost ride the chains home's TOP BAR (release polish 2.1)
Owner refinement on the chains home: the floating top-right chrome row
sat ON TOP of the propositions table — covering content reads badly.
The two survivors (Import notes — always, the entry-point law; the
cost pill when any run exists) now ride the shared TopBar's right
cluster, left of the search input, carved out of the drag band with
lh-no-drag — consistent with the graph view, where they already live in
the top band.

ChainTableBody gains topBarExtra?: React.ReactNode (absent on the
overlay — nothing changes there); ChainsHome's floating chrome row is
deleted; the empty state keeps its own centered import button.

Parked for the next design pass (owner, this round): (1) the pick flow
reads messy — checkboxes show by default, so the pill's select mode is
near-invisible and after Done the Clear/Delete bar + the practice pill
coexist with two Clear buttons; the original vision was checkboxes
appear ONLY when picking. (2) the composer's clearance band has no
outline — the docked area needs a visual edge.

1415 tests (new pin: topBarExtra renders in the bar with the drag
carve; overlay variant injects nothing); tsc, lint, build green.
2026-10-06 08:58:43 +03:00
amosblomqvist 962d3086c0 graph: the rendering switch — chains view as the home (release polish 2)
The settings Graph section splits into two switches (owner, 2026-10-06):

- EXTRACTION (was the lone "Graph" row): the DATA channel — pipeline,
  auditor, prober graph tools. Key unchanged (graph.enabled), label now
  names its job; off = the graph stops growing, nothing is deleted.
- GRAPH RENDERING (new, graph.rendering, default on): display-only.
  Off = the home's main view is the CHAINS VIEW instead of the canvas;
  data, reasoning-chain model, and the pipeline keep working untouched.

The chains home (components/chains-home.tsx): ChainTableBody in flow —
same body as the overlay, same powers (query + sort, selection, the
delete fence with its confirm dialog), but onClose is absent so the
top-bar X does not render (the table IS the page; ChainTableBody now
takes onClose optionally). The chrome survivors float top-right below
the top bar: Import notes (ALWAYS — the entry-point law) + the cost
pill (GraphCostControl, now exported). The composer blob persists over
everything; the table scrolls CLEAR of it via a fixed pb-40 band (text
under a floating box is noise — unlike the canvas, which runs under).

Pick flow mirrors the overlay contract exactly (home lifts the pick
set; selectMode swaps the bar's confirm to Done). The chip relabels to
"Pick propositions" (no "from the map" — there is no map); while
picking, the chip row hides so the floating bar above the composer is
the one voice. Settings resolve rendering through the one core
resolver + renderingEnabled(); read server-side per navigation, so the
switch applies on the next visit home.

Smoke-verified live (headless browser): chains home renders without the
X, import + search + composer clearance confirmed, pick flow checked 2
propositions -> Done -> chip count -> practice session started with the
graph context carried (2 props, 2 chains), settings restored.

Known follow-ups (flagged, not built): after Done the bar morphs to the
table's Clear/Delete management bar while the chip also shows the count
(two voices, same set — the table's own editor law); /chat/new's pick
chip still says "from the map".

Tests: rendering in factory/sanitize/parse/resolve pins, the GraphSection
render pins (Extraction label + rendering row), the home-variant
chain-table pin (no close X). 1414 pass; tsc, lint, build green.
2026-10-06 08:26:55 +03:00
amosblomqvist 8a6eab0b6a titles: smart truncation — the cut snaps to word boundaries
Owner round (2026-10-06, release polish #3): first messages are always
long, and the auto-title's hard 72-char cut landed mid-word. The cut now
snaps to the last word boundary at or under TITLE_MAX_CHARS (72, exported
and tunable); whitespace collapses first so boundaries are single-space.
The one special case (owner-specified): a single word longer than the cap
— rare but real: identifiers, URLs — splits at the cap. A boundary landing
exactly at the cap is a clean cut. Seam unchanged: titleFromPrompt
(core/contracts/graph.ts), applied once server-side at chat creation.
Pinned in tests/title-from-prompt.test.ts (6).
2026-10-06 07:49:38 +03:00
amosblomqvist e6f52d3ad1 docs: the assessment-levels vision (post-release arc) + the 0.3.0 release list
Vision-assessment records the 2026-10-06 thinking round: propositions as
the unit, forecast semantics (mastery = P(correct unaided retrieval)), the
Beta-with-decay estimator (BKT rejected: binary-only, guess-less, never
forgets, unidentifiable at our volume; the weighted average completed with
prior + recency + uncertainty), the e-table over outcome x rung x missClass,
uniform 1/k credit (optional missed-field upgrade unruled), no new outcome
enum values, PRACTICE-ONLY perimeter (ruled — quiz nodes are session-scoped
plan objects, no honest mapping), mastery computed-on-read over the already
durable problem/conclude calls (no stored ledger, graph untouched).

Release-0.3 captures the owner's polish brain-dump (graph rendering toggle
separate from extraction + open home-view question, question keybinds/VIM,
session naming, markdown + graph exports, graph defaults, compaction dialog
Esc law, keyboard audit) and the schema-lock position with the persistent
surface inventory and pre-release gap list (schemaVersion stamps, pi-ai
pins).
2026-10-06 07:34:51 +03:00
amosblomqvist c79b0d4e6e dock: the practice fold morphs like teaching — every docked variant carries its fold state
Owner report 2026-10-06: practice sessions 'open and close instantly'
while teaching morphs. Live instrumented run (Element.prototype.animate
wrapped, three transitions recorded) proved the pose/conclude morphs
fire identically in practice (54px↔327.5px, 120ms, same easing) — the
asymmetry was the FOLD: the shell's height-morph key read
`mode:${pending ? expanded : "-"}` and pending (the question's tool
call) is null in practice, so a practice dock's bar↔form toggle never
changed the key — no height morph, instant jump, only the 160ms content
fade. The ternary predates practice sessions (aeef28d, August); the
remembered Oct-5 fix (ec8315b) added the fade only. Round 16's pause
flow raised fold exposure until the seam surfaced.

Fix: dockStateKey(mode, docked, expanded) — components/dock-state-key.ts
— widened to !!pending || !!practice (the fold machine's own ownership
rule). Typing growth keeps the key (never animates); the fold rides the
exact branch teaching has used for months. Pinned in
tests/dock-state-key.test.ts.
2026-10-06 05:31:00 +03:00
amosblomqvist f82318c9e4 graph: persist the arg_ implication nodes — the splatter fix
Diagnosed (owner report 2026-10-06, 'splattered from the middle on
every navigation'): the canvas's world is 350 nodes — 262 stored
propositions + 88 derived arg_ implication nodes the chain arrows
bend through — but the drag-commit sanitizer accepted only stored
proposition ids, and writePositions REPLACES the file: every
drag-commit purged the arg_ entries, so every mount re-seeded them
on a ~1306-unit ring (seedMissingNodePositions) — arrows streaking
out of the middle, bbox inflated, fit zoomed out. A drag annealed
them back (impLinks springs), the commit stripped them again.

Fix: the sanitizer's known set = deriveView(getGraph()).nodes —
propositions AND arg_ ids (stable: minted from the chain ledger).
The grew-check becomes the honest test — some CURRENT view node
absent from the payload (a merge landed mid-drag) → exactly one
absorbing settle; full coverage → none (the old proposition-count
comparison broke once args counted).

Live ledger backfilled once: 88 arg_ positions seeded at their
chains' centroids, propositions byte-identical (no settle, no
re-anneal). Pinned in tests/commit-positions.test.ts (hermetic
LH_DATA_DIR): args persist, unknown ids drop, no-settle on full
coverage, absorbing settle on a stale payload, garbage sanitizing.
2026-10-06 04:46:26 +03:00
amosblomqvist cbbe1da825 practice: the metadata box rides a body portal — never clipped again (round 17 fix)
Diagnosed: the transcript's MessageContent boxes carry overflow-hidden
(ai-elements/message.tsx) — the absolutely-positioned box rendered fine
over its own message block's content (the closing-words text under the
conclude card) and clipped the moment it crossed that block's bottom
edge onto the next message. Exactly the reported cut-off.

Fix: the box portals to document.body in fixed position (the dialogs'
law) — placed from the trigger's viewport rect at open time, below the
trigger, left-clamped to the viewport; z-50. Any-scroll-close means
there is nothing to track.
2026-10-06 04:31:48 +03:00
amosblomqvist 679188438f practice: metadata popover corrections — no chevron, the old small data register, opens below (round 17 refinement 2)
Owner corrections (2026-10-06): the chevron is gone (Metadata text
alone is the trigger); the data reverts to the original small register
(text-xs rows, mono values — the 15px pass was the misread: the font
question was about the metadata label, not the data); the box opens
BELOW the trigger, always.
2026-10-06 04:28:02 +03:00
amosblomqvist 4eeb05b3c4 practice: the metadata popover — the node-card info-box register, not a drawer (round 17 refinement)
Owner corrections (2026-10-06): no expand/collapse; a quiet METADATA
trigger sits at the card's bottom (no divider — it just sits there);
click opens the info box in the graph-view node card's exact register
(rounded-xl border bg-card p-4 shadow-lg, w-80, floating ABOVE the
trigger so the transcript scroller never clips it); ANY scroll
(capture-phase — every scroller in the tree) or click outside closes
it, plus Esc claimed from the layered chain (usePopoverDismiss, the
popover law). Values match the model solution's font size
(text-[15px], verified — the drawer's text-xs did not); labels keep
the eyebrow register.
2026-10-06 04:26:34 +03:00
amosblomqvist 793638d6ce practice: the agent's data drawer — full transparency on every conclude card (round 17)
Owner ask (2026-10-06): reveal everything the agent filled in when
writing the problem and concluding it, at the very bottom of the
conclude card, collapsible, arriving collapsed.

The fields, both calls:
- problem: mode (recall/discrimination/composition), target
  proposition ids, statement, modelSolution (the last two ARE the
  visible card — the drawer shows the metadata only)
- conclude: outcome, hint rung (R0–R5/null), miss class
  (cue/discrimination/execution/careless/structural/null), retest.

Seams: problemInputForConclude (native-messages) walks the reveal's
walk and folds the WHOLE problem-call input onto the conclude part
(projectView join; same refused-pose law — an unacked pose folds
nothing); ArtifactPartProps grows problemInput. Targets resolve to
proposition NAMES at render (one lazy module-cached /api/graph fetch;
failure degrades to raw ids — observability never breaks the card).
The drawer: divider + chevron 'AGENT'S DATA' toggle (mono-caps
eyebrow register), definition rows with mono values, 'untagged'/
'none' honest empties, absent entirely when the calls filled nothing
(the dismissal line, legacy bare records).
2026-10-06 04:18:26 +03:00
amosblomqvist 0af3514d9b practice: Enter fires next-problem from anywhere, not just composer focus (round 16 refinement 2)
The docks' useEnterSubmits pattern, mounted for the pause in ChatView:
window-level Enter (typing surfaces and buttons excluded, so the
focused composer/button keep their own lane — no double fire) calls
sendNext, which self-guards on nextReady (inert outside the pause;
the dock listeners are unmounted by then — the two never overlap).
Owner's keyboard-first directive: body focus, a scroll, anywhere.
2026-10-06 04:05:38 +03:00
amosblomqvist d1f085550b practice: the next affordance IS the send button — wide green morph in the pause (round 16 refinement)
Owner correction (2026-10-06): the floating chip clipped inside the
composer band. Replaced with the button morph: an EMPTY composer in
the between-problems pause renders a wide green 'Next problem' button
(bg-green-600 — the answered-card correct register, mono-caps label,
arrow glyph); typing anything returns the regular white send; busy
(steer/stop) outranks the morph. Click and Enter share the one
act() lane, so the button also teaches the Enter-on-empty behavior.
The wrapper div + chip are gone from chat.tsx; inputAppends widens
its record param to { messages; kind? } (the practice gate reads it
without a cast).
2026-10-06 03:55:14 +03:00
amosblomqvist 573d89ae66 practice: the between-problems pause — conclude ends the turn, the learner's go-ahead paces the stream (round 16)
Owner ruling (2026-10-06): every concluded problem opens a pause. The
agent may speak its closing words about the learner's last thinking
after the conclude result, then ENDS ITS TURN — a system-prompt law
deliberately, per owner correction, NOT tool-level terminate. The next
problem is never posed until the learner explicitly moves on; the pause
is the session's teaching space (repairs, clarifications, follow-ups —
still surgical, never re-teaching).

The move-on signal is a first-class input: { type: "next" } appends a
practice-next marker (content = single-sourced NEXT_PROBLEM_TEXT — the
model's unambiguous go-ahead: pose per the selection rules or wrap if
the arc is complete). Honest 400s: practice sessions only, nothing
active (mid-problem input rides the dock), riding after any pending
dismissal synthesis exactly like text.

The fast path (owner approved: composer-band chip + Enter-on-empty):
- Composer.enterAction gains "next" — Enter on an EMPTY composer in
  the pause fires the signal; typing anything makes Enter a send again;
  busy (steer/stop) outranks the pause.
- The Next chip hovers at the composer band's top-right (pick-pill
  chrome register) whenever the pause is live and a problem has ever
  been posed; the placeholder becomes the hint ("Ask about this
  problem — or press Enter for the next one…", exact);
- the transcript renders the signal as a compact centered NEXT PROBLEM
  line (mono-caps chrome); session-core appends it optimistically.

CONCLUDE_ACK_TEXT rewritten to carry the law; doctrine §VIII gains the
pause law (owner-sanctioned edit #3); 00-base's conclude/dismissal
bullets rewritten + the between-problems section added.
2026-10-06 03:47:05 +03:00
amosblomqvist a2db80b72d compaction: the usage-anchored trigger + visible session state (round 15)
The relational-databases incident, diagnosed and fixed:

TRIGGER — pi's estimator law, adopted exactly: anchor on the last
assistant message's provider-reported usage (totalTokens || the field
sum) and chars/4-estimate ONLY what was appended after it. The anchor
scan skips aborted/zero-usage assistants and STOPS at the newest
compaction marker (pre-cut usage described context the next request
will not carry — pi's distrust rule). The old estimator
(JSON.stringify of every message /4) tripled the true context
(measured: 84,412 'tokens' estimated vs 38,799 provider-reported on
the incident record) — scaffolding, escaped LaTeX, thinking blocks
and app-side details all counted. estimateMessageTokens now counts
wire text only (text + thinking + toolCall name/args; content text
for user/toolResult/custom). The unanchored fallback runs the plain
heuristic over the CUT-HONORED projection plus the additive prompt
term; the anchored path adds nothing (usage already billed prompt
and tools).

OBSERVABILITY — compaction is a visible session-wide state (owner
spec): the transcript-bottom placeholder register gains COMPACTING
CONTEXT… (BrailleSpinner + Shimmer, the THINKING placeholder's twin,
mutually exclusive by construction — the gate precedes beginTurn),
and the sidebar row shows 'Compacting context · <ago>' with the
spinner (sessionMeta outranks busy; compacting-changed joins
turn-started/turn-settled as refresh triggers). Liveness rides the
API like turn-busy: ChatListItem.compacting + compactionInFlight on
both GET /api/chats and GET /api/chats/[id] (initiallyCompacting
covers the navigate-back-mid-compaction mount — the event already
fired), exported through the server barrel.

Also: the relational-databases record rolled back to pre-compaction
(marker, post-marker turns, tree reset; backup in /tmp) for a clean
retest. The summarizer-usage 'gap' was a misread — steps[] was
populated all along.
2026-10-06 00:38:15 +03:00
amosblomqvist 6dcad55175 compaction: the leading system message is request chrome, never cut (fix the no-tools request)
Owner report (OpenRouter logs, 2026-10-05): the post-compaction request
arrived with NO system prompt and NO tool definitions — everything
before the cut replaced by the compaction message.

Root cause: pi-agent-core's Agent seeds a SystemMessage carrying the
system prompt AND the tool declarations at state.messages[0]
(createInitialSystemMessage — 'messages declare the provider-visible
loadout'). The record never persists it (ui-stream's settleWith filters
the role), but convertToLlm DOES receive it — and the cut projection
sliced from firstKeptMessageId, dropping [0] from the wire: no prompt,
no tools.

Fix (pi's own law — verified against pi-agent-core + pi's coding-agent):
the projection now carries the leading system message through AHEAD of
the compaction head, exactly as pi's prepareRequest re-applies
context.systemPrompt + context.tools around its compaction projection.
The cut slices the RECORD's history; the chrome is never compactable
payload. Loop start skips [0] when chrome is present (no duplicate);
record-path callers are unchanged.

Pinned: chrome-first-through-cut, exactly-once without a cut,
record-path head-first.
2026-10-05 23:52:19 +03:00
amosblomqvist ffc4df1938 compaction: the surface — settings row, two ruled dividers, summary dialog, send gate (round 14 slice 3)
- settings: compaction.thresholdTokens row on the Agent tab (owner pick)
  in a Context group — CommitInput mirroring the graph budget row, hint
  resolved through the SAME core resolver the trigger reads; PUT rejects
  below the 32k floor with the anti-churn rationale; own section
  (compaction) in SETTINGS_SECTIONS + the settings-signal union
- view fold: one marker → two dividers — the minimal MOMENT divider at
  the marker (retrying-message register, no rule lines) and the longer
  CUT divider emitted immediately before the first kept message
  (phase-divider geometry, chip carries '· Nk tok'); both carry the full
  summary payload; full history stays visible to the user
- components/compaction-divider.tsx: the dividers + summary dialog
  (pure exported body for SSR pins, the delete-dialog pattern; Esc +
  Close dismiss, backdrop does not)
- chat.tsx: compaction-changed SSE flips the running state — composer
  canSend, onSend/doSend/doSteerOrSend, and both docks' submit + hint
  ('compacting session…') gate on it (the server's registry mutex is the
  structural gate; this is the affordance); dialog mounts beside the
  abort dialog
- pins: settings-body (4), view fold (2), divider SSR (4)
2026-10-05 22:51:08 +03:00
amosblomqvist dd159cc8fb compaction: the service — settle kick, turn-start gate, registry mutex (round 14 slice 2)
- src/server/compaction.ts: ensureCompacted (the turn-start gate — awaits
  in-flight compaction, compacts synchronously when still over threshold),
  kickCompaction (the settle kick — summary starts inside the learner's
  reading/answering pause; question/problem poses end runs so the pause is
  always a between-runs window), registry mutex on globalThis, anti-churn
  guard (empty-span skip), summary via the zero-tool one-shot sub-agent
  seam on the session's OWN engine (owner ruling), usage to the turns log
  keyed by the marker, record-changed + compaction-changed events
- chat-handler: the gate rides with the BYOK/key gates (before appends,
  beginTurn; record re-read after); the kick at the onFinish tail
  (re-resolves engine fresh; silent no-op when engineless)
- events: compaction-changed {chatId, running} — running=true is the
  client send-gate signal
- settings: compaction.thresholdTokens (factory 150k, floor 32k,
  clamp-never-reject) resolved concrete in EffectiveSettings
- effective threshold = min(setting, contextWindow − 16k) over the live
  catalog index
- overflow recovery needs no special path: the next turn's gate compacts
- 8 service pins (mutex, events order, chained spans, failure re-arm)
2026-10-05 22:17:33 +03:00
amosblomqvist 7e9597d969 compaction: the pure core — cut walk, marker, and the model-facing projection (round 14 slice 1)
- core/compaction.ts: compactionCutIndex (backwards ~20k-token walk, NEVER
  separates a tool result from its call — the cut extends to the call's
  assistant message; a pending pose always survives), estimateTokens
  (chars/4, pi's method), newCompactionMarker (header + summary; details
  carry firstKeptMessageId/tokensBefore/model/at), serializeForSummary
  (labeled text, tool results truncated ~2k), planTreeToText (verbatim
  tree with transcript-derived statuses), buildCompactionPrompt (the four
  ruled sections: learner understanding + evolution, teaching arc, plan
  tree verbatim, current moment)
- toLlmMessages honors the cut: the ACTIVE (newest) marker renders ONCE
  as the head; everything before firstKeptMessageId drops; corrupt cut
  degrades honestly (everything renders, never throw)
- constants: KEEP_RECENT 20k, RESERVE 16k, DEFAULT_THRESHOLD 150k
- 16 test pins
2026-10-05 22:06:14 +03:00
amosblomqvist 0c9f82e027 prompts: Voice + Accuracy become shared fragments — one law across every agent (round 13 part A)
- shared/10-voice.md (agent-neutral) + shared/15-accuracy.md (research-tool
  truth contract) compose for teacher, prober, AND practice
- PRACTICE_SHARED_FRAGMENTS grows beyond 40-formatting.md
- practice 00-base.md drops its stale near-copy ("You have no research tool
  here" was false since round 10); the solve-and-shortcut-hunt line folds
  into the problem paragraph
- teaching system-prompt.md slims (Voice/Accuracy sections removed)
- pins updated: practice-agents composition, prompt-overrides segment mirror
2026-10-05 21:44:34 +03:00
amosblomqvist 4f3bbf61d0 practice: untagged problems — target optional, topic sessions work (owner round 12)
A session whose material is not in the map is a SUPPORTED mode now:
target is optional (absent = untagged problem, poses freely), an
explicit [] heals away at the seam (the record's canonical form is
tagged-or-absent), and when targets ARE present the round-9 law is
unchanged — whole-graph validation, names normalized to ids. The
rejection message teaches the exit ('omit target entirely'), so a
model inventing targets for an off-graph topic converges to omitting
on the retry; omitting always works, no brick is possible. Prompt
carries the clause: material in the map → ids; not → omit, problems
stand on their own.
2026-10-05 20:23:48 +03:00
amosblomqvist 1f7a3516fe ui: drop the filter's bottom margin — the list's top padding is the seam
The filter's mb-2 stacked with the session list's built-in 8px top
padding, doubling that seam to 16px while the others read 8. The
column now runs 12 / 8 / 8 / 8 — the list's own padding carries the
last seam.
2026-10-05 07:13:53 +03:00
amosblomqvist fde858fda3 ui: the drafts go chrome-less; serif titles name sessions, always (owner round 11)
Both /chat/new drafts lose their TopBar — home's register now: drag
mask first in the DOM, floating sidebar-reopen toggle as the only
pointer-events-auto spot, the empty state carrying the orientation.
The chain picker's own bar then sits at the very top of the column,
exactly like the graph view — no stacked bars, no special-casing.
The chain table drops its 'Chains' title with it: the serif title
register is RESERVED — it always names a session; the table names
nothing, so its bar carries counts + search + close only.
2026-10-05 07:09:16 +03:00
amosblomqvist b8c1b9b096 ui: sidebar bands on one 8px rhythm (owner round 11)
The stack read 12/6/4/4/14 — the search→filter seam was the tightest
gap in the column and the filter→list seam 3.5× larger (its mb-1.5
stacked with the list's 8px top padding). One rhythm now: mb-2 on the
create block, the Sessions+search row, and the filter; the list's p-2
top padding IS the rhythm. The 6px inside the create block stays —
intra-group spacing, a different layer.
2026-10-05 07:05:45 +03:00
amosblomqvist d0bb3da7b8 fix: the draft picker fills the content column, not the window
The overlay is absolute inset-0 — it resolves against its nearest
positioned ancestor. Mounted at the NewSession root it spanned the
whole window, over the sidebar and the traffic lights. It now hosts
inside the draft content column's relative container (the overlay
slot on DraftShell) — the same confinement the graph view gives it:
right of the sidebar, under the top bar, inside the session's section.
2026-10-05 06:56:59 +03:00