Commit Graph
92 Commits
Author SHA1 Message Date
amosblomqvist 26bc305d66 ui: plan canvas ink extent + the keyboard-first round
Plan canvas (owner report: arrows clipped at the viewBox edge in dist —
measured 14px/20px cuts in the owner's real plans): blockLayout now
unions node boxes with every edge path's control-point hull (a bezier
never leaves it; the layout calls edgePathD with the renderer's exact
inputs). Dagre's node extent stays the floor, so ordinary plans render
byte-identical; the viewBox origin shifts only when ink bulges past
zero. Verified on both real chats; pins use the owner's real
linear-algebra tree (golden-real-data).

Keyboard-first (owner's law: Enter always submits, arrows always cycle;
text fields the only exception):
- Enter guard split (enter-submits): a focused BUTTON keeps native
  Enter only when keyboard-walked (:focus-visible) or inside an open
  overlay (menus/dialogs); pointer-clicked transcript buttons submit,
  preventDefault suppressing the re-activation underneath.
- Focus hygiene (focus-hygiene): pointer clicks on transient controls
  (agent/user thinking toggles, copy, mermaid expand, plan chip) blur
  to body — the state clicking empty space produces.
- Session search owns the walk: Up/Down wrap-cycle the filtered rows,
  Enter opens the walked row or the first match (quick-open), then
  blurs and keeps the query. The Alt-arrow session cycle is RETIRED.
- Registry: model-menu walk/pick entries removed (obvious in-field
  navigation, not settings material); question-dock keys stay
  displayOnly per owner ruling.

Gates: 1489/1489, typecheck, lint. Verified live end-to-end.
2026-10-10 09:43:12 +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 60971ca6fe vision: the three-part learning model + extensibility direction + the 2026-10-02 roadmap (owner frame doc) 2026-10-03 00:56:02 +03:00
amosblomqvist 26131b3599 push architecture slice 7: graph-status + practice surfaces ride the events channel; coverage polls accepted-by-design; stale poll comments + architecture.md observability section rewritten to the push model 2026-10-02 18:03:47 +03:00
amosblomqvist 91341a93da digest-aware serialized extraction + chain simplification replace the spine stage
Owner live-test verdict on the spine (2026-09-30): no perceptible gain —
the real problems (restatement nodes, shallow re-derivations, same
conclusion from many angles) are mint-time issues a post-hoc chain
minter cannot fix. This arc moves assembly INTO the extractor and adds
the deterministic dedupe the owner specified:

- Digest-aware extraction: each stretch's extractor prompt carries THE
  GRAPH SO FAR (its own source's merged propositions + chains, ids
  verbatim); build on existing ids instead of restating, lean on
  established conclusions as premises. Mixed-namespace chain endpoints
  (fresh p-ids OR existing n_-ids) validated at the submit seam.
- Serialized per source: a source's batches/chunks run strictly in
  order (each extract reads every earlier merge); cross-source runs
  stay parallel; source-wide abort stops the loop. The spine stage,
  role, phase, usage stage, cost row, and setup roster are gone.
- Core-ideas prompt: goal reframed as uncovering core ideas and showing
  how everything derives from the foundations; direction upward, prefer
  lean derivations, never re-derive what the graph already holds.
- Chain simplification (merger maintenance): simplifyChains drops
  provably redundant chains at every merge — prefer-fewest-premises +
  ancestry-cover ([A,B]⟹X with [X,C]⟹Y covers [A,B,C]⟹Y, transitive);
  same-key provenance lines drop together; rewriteLedgers under the
  audit lock; droppedChains counted separately in stats.

Chunk budget stays 8192 (owner: keep for now, maybe lower later).
2026-09-30 17:44:37 +03:00
amosblomqvist 52c02533d1 extraction: the spine stage — cross-stretch assembly between extract and audit
Per-batch pipeline is now extract → spine → audit → merge (owner order,
2026-09-30), the spine inside the serialized audit section so it reads
the graph exactly as the following audit will. Measured motive: 0 of 133
chains in the live corpus crossed a stretch boundary — no stage minted
cross-stretch chains.

- src/agent/subagents/spine.ts: SESSION/NOTES spine preambles + shared
  SPINE_CORE (span doctrine; extraction doctrines carried over), the
  submit_spine TypeBox schema, validateSpine (mechanical span law: every
  chain touches >=1 fresh endpoint AND >=1 existing digest endpoint;
  unknown ids, self-reference, and id collisions are loud), the digest
  prompt builder, and the runner (strong tier, retry-only).
- extraction.ts: 'assembling' phase, spine stage in the per-need gate,
  usage/billing stage, assemble() inside withAuditLock for both front
  doors; first stretch of a source skips the call (empty digest, no
  spend); the audit sees COMBINED propositions (chunk + connectives),
  never chains; merger receives the combined findings.
- core/graph.ts mergeFindings: chain endpoints resolve through the
  job-local map OR pass through verbatim as existing n_ ids (the spine's
  mixed namespace).
- EXTRACTION_CORE: steps-of-reasoning selection bar (examples, notation
  glosses, and restatements that no argument needs are not steps).
- Factory chunk budget 4096 -> 8192 (fewer seams, more local context).
- healStringifiedArrays moved to the shared submit seam (submit-agent.ts).
- Cost panel: Assembly row; usage records carry a spine stage (optional
  on legacy records).
- Tests +5: prompt pins, span-law matrix, digest rendering, seeded
  end-to-end flow (spanning chain + connective, verbatim n_ endpoint,
  spine billed as its own stage), non-spanning refused + retried.
2026-09-30 16:38:45 +03:00
amosblomqvist a2cb1447f7 docs: chains-primary reality in architecture.md + README; two stale comments fixed
- architecture.md section 6 rewritten: stored truth = propositions + chains
  (chains.jsonl); deriveView as the lossy image (implication nodes +
  untyped arrows, rebuilt per read); the bijection framing (independent
  arguments for one conclusion stay distinct — the corruption the edge
  store invited); new deletion-semantics paragraph (proposition death
  cascades to every referencing chain; arg_ death = one argument);
  supports retirement stated plainly; auditor's propositions-only scope;
  clusters stated as birth-session partition truth (no Louvain claim)
- section 7 practice chain model: stored-chains inclusion law replaces
  follows-edge grouping; chainsToGraph bijection claim deleted
- README: graph clauses (stored chains, derived view, chain dedupe),
  practice clause, ledger inventory, and two long-stale line-5 claims
  (ledger events, semantic top-K) corrected
- contracts/graph.ts GraphSnapshot comment: Louvain claim replaced with
  birth-session truth; extraction.ts comments no longer narrate the
  dead expandFindings/chainsToEdges seam
2026-09-30 03:27:10 +03:00
amosblomqvist a3aabb3a57 extractor redesign: chains are the deliverable (owner-approved, goal-first) 2026-09-30 02:09:25 +03:00
amosblomqvist 08e64466ad chain line: the logic is the connective — LaTeX ∧ and ⟹ via KaTeX (owner ruling)
'A → B → C' misread as chained implication when A and B are
CO-premises. The honest shape (owner, 2026-09-30): premises join
with the LaTeX LAND (∧), the conclusion separates by the LaTeX
IMPLICATION (⟹) — A ∧ B ⟹ C.

The glyphs are REAL KaTeX renders — components/latex-glyph.tsx wraps
katex.renderToString (cached per command; the app's math engine, the
stylesheet already rides app-wide from app/layout.tsx). No SVGs, no
font-dependent unicode. PROMINENT in the theme foreground (white in
the dark theme) per the owner's same-round ruling — the logic is the
payload, not the old dim gray. A lonely chip carries no glyph (it
reads as a standalone proposition); the plain-text '→' survives only
in the row's aria-label (formatChainLine, the screen-reader form).
2026-09-30 01:35:59 +03:00
amosblomqvist e7f4923159 deletion screen minimal + 'node' as THE UI word (owner ruling)
Owner 2026-09-30: the confirm dialog's pre-flight impact read had to
go — what full death takes is obvious, the per-open fetch flickered,
and richer facts would only confuse as deletion semantics evolve
(rewiring is on the table). The screen is now: Delete node? / Delete
N nodes?, the names in the mono block, one permanence line, Cancel /
Delete. No loading state, no gone state (a 404 mid-run lands in the
honest failure line), no sessions join — the joins prop died too.

The dead path is deleted end to end: GET /api/graph/nodes/[id],
graphNodeImpact (server), nodeDeletionImpact (core) and their tests.

WORDING: node/node(s) is THE UI word for graph objects, standardized
across every surface's copy (owner, same ruling): the dialog titles,
the chain table's counts/search/summary/delete aria-labels, the
practice dialog's cluster counts, selection line, and empty-cluster
hint. Internal identifiers (selection.propositions) unchanged.

Rewritten dialog pins (tests/delete-node-dialog.test.tsx): minimal
contract — no facts/loading/fetch copy, singular/plural titles, the
cap, 409 dock phrasing, busy states, no 'proposition' left in the
dialog.
2026-09-30 01:19:03 +03:00
amosblomqvist 40dc1b7f85 chain table: the fenced Delete action + multi-node delete dialog (owner ruling)
"This is really the view deletion should be done in" — the chain table
becomes the graph's MANAGEMENT surface. The floating selection bar
gains Delete FENCED at its right end (owner-approved placement): a
divider quarantines the destructive action from Clear and Generate,
red destructive pair + trash icon, opening the SAME full-death confirm
dialog the canvas uses.

DeleteNodeDialog extended from one node to an array:
- one node renders exactly as always; many pluralize the title
  (Delete N propositions?) and list the names in the mono block,
  capped at 8 + 'and K more'
- the pre-flight impact read aggregates: summed edge counts, unioned
  referencing sessions (deduped, first-seen order); all-404 = gone
- confirm runs the per-node DELETEs SEQUENTIALLY — a failure stops
  the run (extraction_running is never plowed through); onDeleted
  now carries every id that actually died
- the chain table prunes its selection on delete and announces
  lh:graph-revision so the snapshot refetches

Drift fix found en route (surfaced as NaN in the aggregated facts):
the server's NodeDeletionImpact has been {edges, sessions} only since
the quiz-events ledger died in the practice-arc schema slim — the
dialog kept rendering quizEvents/observations as undefined. The dead
fact lines are removed; the dialog now shows exactly what the server
can honestly report.
2026-09-30 01:03:59 +03:00
amosblomqvist 4d58aa74b1 top bar drag standardization: auto carve-out + the overlay DOM law (owner ruling)
The chains-overlay TopBar had dead drag zones (counts, around search,
close gap) — cause: app-region subtraction resolves by DOM ORDER, not
z-index; the graph chrome's lh-no-drag column came later in the DOM and
punched holes through the overlay's drag band despite sitting visually
underneath (z-40 vs z-10).

Two laws, per the owner's standardization ruling:
- AUTO CARVE-OUT (globals.css): inside any .lh-drag band, every native
  interactive element (button/input/textarea/select/a/summary/
  [contenteditable]) is automatically a no-drag island — draggable
  anywhere that is not a clickable element becomes structural. Manual
  lh-no-drag is only for clickable non-natives.
- THE OVERLAY DOM LAW: a surface containing a TopBar that covers other
  content must be the LAST child of its container — ChainTableOverlay
  moved after the chrome column (verified live: last child, after the
  column; auto-carve rule compiled; search/close/sidebar-toggle all
  still click).
2026-09-30 00:44:08 +03:00
amosblomqvist edb10e3791 chain table round 6: the thread bracket law (owner refinement)
The tree threads no longer touch their arrows. Each thread is
BRACKETED by checkbox bottom edges (live-measured anchors):
- starts at its parent row's checkbox bottom edge — a visible gap
  below the arrow's stroke (the stroke ends ~2.4px above the anchor)
- terminates exactly at the LAST child row's checkbox bottom edge
  (cluster: top-[23px]/bottom-[8px] incl. the 1px group rule; chain:
  top-[22px]/bottom-[8px]). Verified live: 23=23 top anchor, bottom
  ends within 0.13px of the last checkbox at both levels.
2026-09-30 00:37:55 +03:00
amosblomqvist 40b0c33f93 chain table round 5: tree threads + the framed table (owner trial)
- TREE THREADS: open folders and chains drop a vertical guide from
  their expander arrow straight down through their children — at the
  arrow's own column (37px clusters / 73px chains), subtle border/50.
  Live-measured pixel-exact: arrow center = thread x (325=325, 361=361).
- The framed table: a rule under the header and under the last group
  join the between-group separators; rows stay lineless.
2026-09-30 00:28:47 +03:00
amosblomqvist b85aab6eb0 chain table round 4: double the indentation (owner refinement)
Each staircase step is now TWO columns (36px): chains pl-12 — the
checkbox aligned with the cluster's FOLDER icon — and statements pl-21
— the checkbox where a chain icon WOULD sit (the chains lost theirs;
the pattern continues anyway). Live-measured: chain checkbox = folder
icon (336=336); statement checkbox = chain checkbox + 36 (372).
2026-09-30 00:23:15 +03:00
amosblomqvist d6a7228b15 chain table round 3: waypoints icons gone + the alignment staircase (owner refinements)
- chain rows lose the WaypointsIcon (owner: not needed — the chips +
  connectors already read as chains); folders keep their icons.
- THE ALIGNMENT STAIRCASE: every level's checkbox sits directly under
  its parent's expander arrow. The expander now shares the checkbox's
  14px width (h-5 w-3.5), making each nesting step exactly 18px —
  clusters pl-3 (12px) → chains pl-7.5 (30px, the cluster expander) →
  statements pl-12 (48px, the chain expander). Live-measured: 318=318,
  336=336.
2026-09-30 00:15:47 +03:00
amosblomqvist 4fa6e2105a TopBar standardization + chain table round 2 (owner refinements, 2026-09-29 late)
- components/top-bar.tsx: THE shared top bar (standardization ruling) —
  h-15, lh-drag + carve-outs, select-none, SidebarToggle reopen with the
  Electron traffic-light gutter; adopted by chat (rename keeps its
  drag={false} escape hatch), new-session, practice-list, practice-exam,
  and the chains overlay.
- chain table: prominent indentation (pl-11 / pl-[4.75rem]), the ONLY
  lines are the separators between cluster groups (row + vertical lines
  gone), plain statement rows (no conclusion marker), all three columns
  sortable (Props too) with a small chevron on the active header.
2026-09-30 00:02:24 +03:00
amosblomqvist e2109d1c29 chain table redesign (owner refinements, 2026-09-29 evening)
- full-bleed: blur + translucent surface IS the overlay, no inner card;
  own top bar (info + search + close, lh-no-drag so home's drag mask
  never eats the X — the clickability bug)
- three levels, one click each: cluster (birth session; a chain lives in
  its CONCLUSION's cluster) → chain → proposition STATEMENTS revealed
  directly (names never twice; singletons one click); all collapsed by
  default
- proposition names as outlined chips, → connectors between; lonely chip
  = standalone proposition — the '(no premises)' annotation is dead in
  the visible UI (survives only as aria-label); Folder/Waypoints icons;
  rotating chevrons; deep statement indent
- real table look: hairline row + column lines; sortable Name + Created
  headers (click toggles asc/desc; size is text, not a control)
- selection never shifts layout: floating bottom bar (Google-Photos
  pattern) with N propositions → M chains + Clear + Generate
- fuzzy search grouped + auto-expanding clusters

live-verified end-to-end on the owner's dev server; 15 rewritten render
pins; gates: tsc 0, 1179/1179, lint 0, build 0
2026-09-29 23:21:51 +03:00
amosblomqvist 155d065fd1 practice: the chain table — every reasoning chain in one selectable overlay (owner-approved plan, 2026-09-29)
- core formatChainLine: names + arrow one-liner, singleton marker, pure
- graph-view Chains button (chrome row, empty-graph hidden) opens the
  overlay: fuzzy search (names/aliases/statements), collapsible chain →
  member → statement rows, birth-session + created columns, no
  understanding-levels column
- pinned Selected section derives the agents' actual input live
  (chainsForSelection over the selection; premise without its
  conclusion shows as the singleton it becomes)
- Generate opens the creation dialog in CUSTOM mode: selection summary
  + dated default title, posts {nodeIds, params, note}; cluster mode
  untouched on /practice
- live-verified on the owner's dev server: 219 chains, derived preview,
  search-never-filters-Selected, dialog handoff (selection pass-through
  bug caught + fixed en route), Esc/backdrop dismiss, zero writes
- practice UI files re-normalized to tabs (my earlier prettier runs had
  drifted them to the 2-space default; whitespace-only)

17 render/unit pins added; gates: tsc 0, 1174/1174, lint 0, build 0
2026-09-29 22:41:52 +03:00
amosblomqvist 06032769d2 practice: the learner's creation note — optional free-form input carried to partitioner + generators (owner, 2026-09-29)
- dialog: optional 'Anything else?' textarea (typing-only, 2000-char mirror)
- API: body gains note? — trimmed, whitespace-only omitted, hard 400 over 2000
- pipeline: note persisted on the set and threaded to both agent stages
- agents: THE LEARNER'S NOTE prompt block (present only when set) + the
  weighed-never-owed stance pinned in both doctrines
- fix en route: doubled THE PARAMETERS heading in both prompts since slice 3
  (renderParams owns the heading; the literal blocks removed)
2026-09-29 22:16:28 +03:00
amosblomqvist cf0ec4a2f0 practice UI round — top bars + sidebar reopen, one-attempt per-problem submit with instant reveal, score-only grading (owner 2026-09-29) 2026-09-29 21:54:32 +03:00
amosblomqvist 88eb4b2ec0 practice restructure — per-problem jobs + ONE shared background-LLM pool (owner ruling 2026-09-29)
Rulings: each problem generation is its own visible dock job (the
extraction per-chunk pattern), and practice shares extraction's
semaphore — one process-wide 3-permit pool for ALL background LLM
jobs; total background LLM concurrency bounded at 3.

A. SHARED POOL: new src/agent/subagents/bg-llm-semaphore.ts —
   globalThis __lhLlmSlots, PERMIT_COUNT 3 (the ruling), the
   fresh-take/handed-off-permit deadlock discipline preserved for
   BOTH callers. extraction.ts's withExtractSlot is now a thin
   wrapper (job-row signal → withBgLlmSlot); __lhExtractSlots and
   __lhPracticeSlots both die. The partitioner now acquires a permit
   too (it's a background LLM job — it didn't before).

B. REGISTRY ROWS: the aggregate setId:generation / setId:grading rows
   are replaced by setId:partition (phase partitioning, label
   'Partitioning — title'), setId:gen:problemId (queued→generating,
   'Problem i/N — title'), setId:grade:problemId (queued→grading,
   'Grading i/N — title'). Each row carries its OWN abort controller
   and its OWN usage (one PracticeStepLog per row — the aggregate
   partitioner/generator/grader folding dies). Row key = feed id =
   abort key (the dock dup-key law).

C. ABORT SEMANTICS: abortPracticeRow(key) routes by shape — gen row:
   THAT problem only (error 'Aborted', siblings continue, set settles
   ready with one errored problem); partition row: the whole run;
   grade row: that grade drops (answer kept, re-submittable).
   abortPractice(setId) composes from row aborts + the synchronous
   persisted marks (a set-abort's verdict stands before the settles
   re-read it — the settle step keeps an existing error status).
   /api/activity/abort kind practice now takes the ROW key (charset
   admits ':').

D. FEED + DOCK: ActivityPractice/FeedPractice carry per-row identity
   (id = row key, problemIndex/problemTotal); dock blobs render per
   row, labels carry the i/N; the aggregate row is gone.

E. Boot sweep unchanged at set grain; the live-row check is now a
   key-prefix scan (any live row of the set protects it). The
   updatedAt heartbeat covers the new row scheme as-is.

Tests (20 in the pipeline suite): per-problem row shape/uniqueness/
i-N labels (partition row frozen behind a gated partitioner; per-row
usage ISOLATION pin — b's log never shows on c's row); abort-one-gen
(sibling completes, set ready with one errored problem); partition-
row abort; queued grade-row abort (dead-ticket path: answer kept,
re-submittable, sibling lands); shared-pool identity + contention
(practice queues behind extraction-style holders); registry/dock
feed mapping. Test isolation hardened: unique per-store set ids,
afterEach registry/lock/pool reset (a timed-out test can no longer
poison the rest).

architecture.md: jobs-pattern paragraph rewritten (per-problem rows,
shared pool, abort routing).

1149/1149, tsc 0, lint 0, build 0.
2026-09-29 20:42:33 +03:00
amosblomqvist 25cc2a238a practice — formatting doctrine for generator + grader (owner-approved 2026-09-29)
Prior state: zero formatting guidance anywhere in the agent module —
models wrote prose-only by default.

GENERATOR_DOCTRINE Part III gains the FORMATTING bullet (after the
THE PARAMETERS bullet, before PREVIOUS PROBLEMS): statement, solution,
and rubric render as full Markdown — math always LaTeX (inline $…$,
display $$…$$), code/pseudocode/formal systems in fenced blocks with
a language tag, identifiers inline as `code`, tables for parallel
structure, lists for enumerations; prose-only renderings of
mathematical or structured content are a defect.

GRADER_INSTRUCTIONS gains the feedback-formatting paragraph (before
the OUTPUT section): full Markdown, LaTeX math, fenced code, lists/
tables where they carry structure — teach the boundary in properly
formatted prose, not plain text.

PARTITIONER gets nothing (machine-shaped output).

Tests: substring pins on the exported doctrine constants — the exact
FORMATTING sentence + ordering (after presentation, before previous
problems), the exact grader paragraph + before-OUTPUT ordering.
Architecture.md §7 one clause (the formatting doctrine in the
generator's prompt inventory).

1144/1144, tsc 0, lint 0, build 0.
2026-09-29 20:21:12 +03:00
amosblomqvist 8d39f3717c practice bug fix — the boot sweep stamped a LIVE run (owner live test 2026-09-29)
Owner report: created a set, 'Interrupted (server restarted)' showed
on list + set views, generation proceeded and overwrote it. The
sweep's 'generating + no registry row' test had a false-positive
mode: it ran at EVERY pipeline instantiation, and an instance could
evaluate against a registry whose fresh row it couldn't observe.

Three guards, per ruling:

1. HEARTBEAT: ProblemSet.updatedAt? (contract + doc) — bumped on
   every store write; createSet stamps it = createdAt; saveSet
   re-stamps (validator tolerates absence for legacy rows).

2. ONCE PER PROCESS: the sweep guards on globalThis
   .__lhPracticeSweepDone — no instance re-evaluates after the
   process's first sweep.

3. GRACE WINDOW: only sets whose heartbeat (updatedAt ?? createdAt)
   is older than 120s are stamped. A live run saves at every phase/
   problem transition, so it is structurally unreachable; a true
   orphan is minutes stale at next boot. One [practice <id>] log
   line per stamped set (the extraction/jobs console.error idiom).

Tests: stale orphan stamped; FRESH generating set untouched (grace);
once-guard (second factory instantiation never re-sweeps); live-row
protection re-armed via resetSweep() test seam; store pins for
updatedAt birth-stamp + re-stamp + legacy tolerance. Architecture.md
sweep sentence updated (once-per-process + grace semantics).

1142/1142, tsc 0, lint 0, build 0.
2026-09-29 20:19:46 +03:00
amosblomqvist e92679254d practice — discretized parameter axes: named levels, never a number (owner-approved 2026-09-29)
Contracts: LadderRung 0-4, CueDistance 0-2, Presentation theory|
application; PracticeParams carries the three named levels. Doc
comments hold the three definition tables verbatim (rung names +
definitions from the ladder doctrine; cue levels; presentation
levels).

Core: LADDER_RUNGS/CUE_LEVELS/PRESENTATIONS tables in src/core/
practice.ts (definitions verbatim) — ONE source spoken by the agents'
prompts, the dialog's stepped controls, and the exam header; core
barrel re-exports practice.ts (pure domain, browser-safe).

Agents: Part I's ladder intro now 'five rungs, numbered; the request
arrives as a rung' with bullets numbered 0-4 (definitions untouched);
Part III's parameters block replaced with the named-level doctrine
(rung = exact class, no continuum; cue counts lesson examples AND
previous problems' situations; presentation dresses, never changes);
partitioner's RESPECT THE PARAMETERS replaced (low rungs = many small
assignments, high = larger; far cue makes confusables-together MORE
important; presentation does not shape the partition). renderParams
outputs the exact named-level block. Anchor/0..1 language swept dead.

Service + store: strict union validation — floats between levels,
out-of-range, wrong strings, legacy dial shapes all 400 at the API
and fail isProblemSet (legacy sets skipped from lists; no migration —
data/practice is disposable scratch, owner wipes before testing).

UI: dialog = 5-detent ladder slider + 3-detent cue slider (each shows
the level NAME + one-line definition) + Theory|Application segmented
toggle; defaults rung 2 / cue 1 / theory; Create posts the new shape.
Exam header gains the levels line ('Direct exercise · Adjacent
context · Theory').

Tests: renderParams exact-block pin; strict-validation pins (new
tests/practice-service.test.ts — bad unions 400, legacy shape 400);
store pins (legacy floats + 2.5 rung fail); render pins (detents +
names + definitions, defaults, detent-swap, toggle active segment,
levels line); every params fixture moved off floats. 1138/1138, tsc
0, lint 0, build 0.

Docs: architecture.md — discretization paragraph (compact tables,
named-level invocation, defaults, one-table source), UI mentions;
README axes sentence.
2026-09-29 20:06:35 +03:00
amosblomqvist a53127a764 practice fix follow-up — the picker lists EVERY birth-session group (owner ruling)
Owner escalation on deviation 1: practice availability must not
inherit a visual-rendering threshold. graphClusters() now runs
birthSessionPartition(snap.nodes) directly (same size-desc/id-asc
ordering) instead of reading the snapshot's MIN_CLUSTER_SIZE-filtered
clusters array — MIN_CLUSTER_SIZE stays a hull-drawing rule only.
Label join mirrors the snapshot's (chat title / notes path, null when
the naming record is gone). Contract doc + architecture.md sentence
corrected to match; empty-picker copy reworded ('once a session births
its first proposition' — no 3+ mechanics). Render pins: new empty line
+ a 1- and 2-proposition group listing pin. 1130/1130, tsc 0, lint 0,
build 0.
2026-09-29 19:28:51 +03:00
amosblomqvist f39e1b4294 practice fix — creation is graph-domain: cluster picker, nodeIds-only API (owner ruling 2026-09-29)
Owner ruling: 'problem sets always take in nodes — graph objects' —
practice is graph-domain, not chat-domain. No session-created sets.

API: POST /api/practice/sets takes {nodeIds, params, title?} ONLY —
the chatId branch, UnknownChatError, and the any-provenance filter are
deleted; the selector is nodeIds (readNodes filtered to the selection,
unknown ids drop, empty live intersection → 400 empty_selection);
title optional from the UI (the picker's cluster label), dated default
stays. chatStore leaves the pipeline deps entirely (labels resolve
store-side in the clusters service).

GET /api/graph/clusters (new): the birth-session groups the graph view
renders — graphSnapshot's clusters verbatim (same ordering, same
MIN_CLUSTER_SIZE presentation filter), mapped to {key, kind
chat|notes, label (null when the naming record is gone — honest,
never guessed), nodeIds, nodeCount}; corrupt/empty graph → empty
list. GraphClusterGroup contract in core/contracts/graph.ts (client-
safe — the dialog imports it).

practice-dialog: session picker → cluster picker (rows show label +
proposition count, notes clusters marked); Create posts the cluster's
nodeIds + label as title + params. 400 empty-selection copy reworded
for the vanished-nodes edge ('Nothing practicable is left in that
cluster — the graph changed.'); 503 deep link unchanged. Preselected
mode gone — the dialog is the only creation entry for now (the graph-
view cluster entry comes later with free-form selection).

Sidebar: session right-click 'Practice this session's propositions'
removed (item + state + dialog wiring); the /practice nav row stays.

Tests: pipeline — chatId/any-provenance tests die, nodeIds selection
pins added (unknown-id filtering, dated-default title, title hint
rides, empty/vanished → EmptySelectionError); render — cluster picker
pins (label+count rows, notes kind, unlabeled cluster muted, empty
graph line), preselected pins removed; 16 render + 13 pipeline.

Docs: architecture.md §7 API+UI paragraph rewritten (graph-domain
ruling, /api/graph/clusters, cluster picker as the only entry, no
404-unknown-chat); README creation-flow line updated.

1130/1130, tsc 0, lint 0, build 0.
2026-09-29 19:26:37 +03:00
amosblomqvist 451caba1b1 practice slice 6 — docs: architecture practice section, README feature line
docs/architecture.md: new §7 'Practice — strengthening, not assessment'
(purpose ruling + parked exam machinery; the chain bijection with
follows-edges, supports invisible, exactly-once partition; self-
contained sets frozen at creation; two-stage generation — partitioner,
one generator per problem with YOUR TARGET echo law + THE FULL SET
minimal-use doctrine, commit-before-pose, failure isolation, dedup
across sets; feedback-only grading; registry-not-JobRecords jobs
pattern and WHY — not chat-scoped, never wakes the teacher, boot
sweep; API + UI surfaces; three strong-pinned roles on the shared
runSubmitAgent seam). Sections 7-13 renumbered to 8-14; the §9
cross-ref in the deferred list updated. Cross-referenced enumerations
updated: §1 six-systems table + row, §2 layer count 25→28 routes,
§2.2 seven contract files + practice.ts, §3 PracticeStore port +
adapter + composition-root bullets + PORTSBOX, §4 six-systems heading
+ PRACTICE mermaid box + practice-request protocol, §6 practice-
territory sentence now points at the landed module, §10 observability
practice rows + abort kinds, §11 testing strata, §12 seam directory
row.

README.md: practice paragraph in Key mechanisms at the same grain
(feedback-only problem sets, partitioner/generator/grader, registry
jobs); the graph paragraph's 'assessment becomes practice-module
territory' forward-reference resolved to the landed module.

Docs-only — no code changes. 1129/1129, tsc 0, lint 0, build 0.
2026-09-29 18:44:15 +03:00
amosblomqvist 8355fbabdb delete the mappings ledger — per-job in-memory resolution + notes fingerprint guard
The persisted (chatId, sourceId)→node table was a shared namespace across
independent chunk/batch jobs: each chunk's extractor mints local ids fresh
(p1..pN), so later chunks collided with earlier ones — propositions silently
skipped as 'already resolved' (coverage checkpointed anyway: permanent silent
loss) and edges resolved onto the wrong nodes (the 'everything links to
everything' mush). Owner ruling 2026-09-29: mergeFindings resolves THIS job's
ids in memory, nothing persists between jobs; cross-job identity is the
auditor's linkTo verdicts alone.

- contracts: NodeMapping dies; NotesSource.contentFp — sha256 of the text at
  the last run, the re-import guard (unchanged re-import = TRUE no-op, no
  model calls, no usage; changed text re-runs; legacy unstamped sources
  re-run once)
- merger: job-local resolution map, in-submit duplicate ids skip once,
  edges resolve extractor-local ids only
- ports/store: appendMapping/readMappings, LEDGER_FILES.mappings,
  rewriteLedgers.mappings gone (stale data/graph/mappings.jsonl just stops
  being read)
- session-clusters: birthSessionOf is provenance-only — the legacy
  mapping-line fallback for provenance-empty notes nodes dies
- deletion: no mapping rows to strip; stats lose mappingsRemoved
- replay-layout batches nodes by first provenance point
- graph.test pins the new independence semantics (re-merge re-creates;
  duplicate ids within one submit resolve once); extraction tests pin
  per-chunk merges + the fingerprint guard
2026-09-29 02:25:32 +03:00
amosblomqvist 3d9d20794c Auditor submits links ONLY — the edges argument is gone (owner, 2026-09-28 evening)
Hypothesis under test by the owner: coherent extractions turn to mush
after merging because the auditor minted too many bridging edges of its
own — inference, not identity, and out of the auditor's scope. The graph
now connects across sources solely through identified equivalences;
relations between propositions come from the extractor alone.

- auditSchema drops 'edges' (submit_review = links only); the submit-seam
  required-field doctrine reworded per tool
- AUDITOR_INSTRUCTIONS: the BRIDGING EDGES section dies; deliverable is
  the identity verdicts only
- AuditVerdicts drops edges; ExtractedEdge endpoints are extractor-local
  ids only; mergeFindings stops consuming verdict edges and its resolve()
  drops the raw-node-id branch (dead — the extractor never sees graph ids)
- extractor untouched: submit_findings keeps propositions + follows/
  supports edges, required as before
- tests: 17 submit fixtures + 9 typed verdict literals slimmed to links
- docs: architecture.md division-of-trust + pipeline diagram updated
2026-09-29 01:16:32 +03:00
amosblomqvist d047e7b2dc Per-chunk jobs: parallel extracts under a 3-slot semaphore, serialized audits (owner 2026-09-28)
Every notes chunk and chat coverage batch is its own job — registry row,
usage record, failure domain. Extracts fan out process-wide behind a
3-permit extract semaphore on globalThis (new 'queued' phase); audits and
merges stay serialized behind the existing FIFO gate ('waiting'), so each
auditor explores the graph after every earlier job's merge.

- ExtractionPhase gains 'queued'; RunningExtraction gains part {i,n};
  the feed shows one row per job with a '· i/N' suffix (status route)
- abortExtraction is source-wide: every job of a fan-out dies together;
  merged jobs keep their coverage checkpoints
- failure isolation at the fan-out (Promise.allSettled + aggregation):
  a failed chunk no longer kills its siblings — the run returns what
  actually merged; job-level aborts log, pre-fan-out errors still throw
- usage ledgers: one record per job (source-keyed), failed jobs bill
  their partial spend
- runJob/aggregateJobs helpers; setPhase/recordStep/extract/audit/
  notesExtract key the registry by jobKey (src#i) — notesExtract's
  signal lookup was the one real bug the new tests caught (source-id
  lookup meant fan-out aborts never reached the model call)
- semaphore is handoff-correct (fresh takes increment; handed-off
  permits don't double-count — an inflated counter deadlocks at 3)
- tests: isolation pins rewritten (stage failure now resolves zeros),
  single-flight/registry tails updated, per-job usage counts, and a new
  describe pinning the fan-out: 3 extracting + 5 queued, parts 1..8,
  source-wide abort, per-job usage on failure, extract peak == 3,
  audit peak == 1
- docs/architecture.md: serialized-audit paragraph rewritten for
  per-chunk jobs; two-front-doors wording aligned
2026-09-29 00:34:21 +03:00
amosblomqvist 3e9a85d60f per-session models: every session owns its model + thinking level from birth
The reported bug (2026-09-28): every chat ran glm-5.3-flash regardless of
any pick, while composer and settings UIs agreed with each other and lied
about the run. Root cause: a fossil default-settings record (settings.json
chat section, frozen pre-slots-wave) was stamped verbatim into every new
chat at birth by the new-session flow, and resolveConversationSpec prefers
the per-chat record over the strong slot forever.

Owner redesign — models are per session, always; the strong slot is the
new-session default:

- POST /api/chats stamps the newborn verbatim from the strong slot
  (model + thinking level); no client settings PUT on create anymore
- the composer control is dual-bound: in-session it reads/writes the
  session's own record (GET /api/chats/:id, PUT .../settings); on the
  draft surfaces it binds the strong slot (picking there = changing the
  default)
- thinking levels follow the identical mechanics (confirmed by owner)
- the zombie default-settings system dies whole: useDefaultSettings,
  /api/settings/defaults, src/store/default-settings.ts, the chat
  section of UserSettings (sanitizer strips fossil copies), the legacy
  default-settings.json fold, and DEFAULT_SETTINGS_FILE
- catalogDefaultsFor collapses to the strong slot alone
- thinking display for a legacy unstamped session resolves exactly like
  the engine (session ?? strong ?? model ladder)

Tests: new chat-birth-stamp suite pins the stamp, the unconfigured boot,
the unset level, and the copy-not-live-view regression; defaults pins
rewritten strong-only; legacy-migration suite deleted.
2026-09-28 23:30:11 +03:00
amosblomqvist 1f5e21a674 teacher-prose-only extraction view: coverage, fingerprints, tokens, and batches derive from the teacher's text blocks alone (owner 2026-09-28) 2026-09-28 22:37:18 +03:00
amosblomqvist c17405a3d7 chunk notes like chats; both submit tools require edges/links (owner 2026-09-28) 2026-09-28 21:28:39 +03:00
amosblomqvist f11b1664dc verdicts-only auditor: submit_review is {links, bridging edges} — the findings echo dies
Owner ruling 2026-09-28: the auditor has no control over the
extractor's output and never alters it, so re-emitting the full
findings per submit was pure payload cost — the retry collapse the
schema slim attacked, halved again. submit_review is now links
(local proposition id -> existing graph node; EQUIVALENCE BIAS
carried in the linkTo description) + bridging edges ONLY; the
merger joins extractor findings + verdicts.

mergeFindings(chatId, findings, verdicts, ...) — propositions come
from findings verbatim, links join by id, edges emit extractor's
then auditor's through one dedup-preserving emitter. The old echo
form hid an accidental drop channel: an auditor that omitted a
proposition from its echo silently dropped it from the merge.
Verdicts-only removes it structurally — extractor output always
fully merges (tests updated to full-merge expectations).

AuditResult/AuditedProposition -> IdentityLink/AuditVerdicts;
audit() logs links/bridges; batch view renders anchorless like
transcriptView; auditor prompt rewritten to the verdicts-only
deliverable; architecture.md division-of-trust updated.
2026-09-28 19:22:58 +03:00
amosblomqvist bd9694b0e4 slim the submit schemas: evidence and messages die; stringified-payload heal at the pre-validation seam
OpenRouter log diagnosis (owner, 2026-09-28): extraction time was
dominated by tool-validation retries. The failing call had the whole
propositions array emitted as ONE JSON-STRING ('propositions.0: must
be object'), a truncated placeholder artifact inside it, and
word-salad statements late in the array — payload-size collapse.
Field audit: 'evidence' was never persisted anywhere and its
faithfulness-checking consumer died with the fully-trusted ruling;
'messages' fed messageId granularity that NOTHING downstream reads,
and the model sent [] anyway (chat-level provenance in practice).
'aliases' stays (optional now): the auditor's find tool globs them.

Schema: propositions = {id, name, statement, aliases?}; edges =
{from, to, type}. Prompts lose the evidence/messages doctrine and
[id]-anchor guidance, gain a compact faithfulness line;
transcriptView renders anchorless. The auditor echo instruction
drops the evidence/messages mention, so its payload halves too.

Heal: healStringifiedArrays on the submit tool's prepareArguments
(pi-agent-core's sanctioned pre-TypeBox shim) parses a stringified
propositions/edges back into arrays — the LaTeX-restore doctrine:
heal weak-model wire behavior at the seam. Test drives the real
agent-loop seam with a stringified submit.
2026-09-28 19:04:39 +03:00
amosblomqvist 46e128d46c serialized audit: the audit+merge critical section is process-wide, one run at a time (owner 2026-09-28)
Extraction may run in parallel across sources (extractors are
source-local and graph-blind), but the auditor explores the LIVE
graph through its tools and stamps identity verdicts against what
it finds — two concurrent audits would each link into a graph the
other is about to change, the structural mint of duplicate nodes.

withAuditLock: a FIFO promise-chain mutex on globalThis (the same
module-instance idiom as the run registries — a module-level lock
would be per-route-instance and useless in dev). The check runs
before any LLM is invoked or the graph read; the lock spans audit
through merge inclusive (the merge is the graph mutation) and is
reacquired per batch, so interleaved multi-batch runs each audit
against the latest graph instead of starving each other. Applied at
both front doors — chat batches and notes imports.

New 'waiting' phase (extracting → waiting → auditing → merging):
visible in the status feed so a queued run never looks stalled in
extracting. Abort-aware on both sides of the queue: a run aborted
while waiting leaves without auditing (its chain link still
releases — successors never hang); a run aborted mid-audit releases
in finally.

Tests pin: a second run queues at the gate and its auditor is
invoked only after the first run's merge is in the ledgers (the
post-merge-graph invariant itself); a run aborted while waiting
never audits, checkpoints nothing, and leaves a healthy chain for a
third run. Gates: typecheck, 1061/1061, lint, build.
2026-09-28 18:00:56 +03:00
amosblomqvist fc8d91a043 first live-extraction findings: fix backwards edge-section labels; extractor naming/self-merge/completeness doctrines
Owner's first live test of the relinked pipeline surfaced three issues,
diagnosed against the ledgers:

1. Detail-card edge labels were backwards (UI only — data and canvas
   arrows were correct). Semantics pinned 2026-09-28: an edge A→B
   means A implies B (follows) or A supports B (supports). Outgoing
   edges now list under 'What follows from this' / 'What this
   supports', incoming under 'Follows from' / 'Supported by'.
   Verified against the ledger: Invariance --supports--> Twin
   paradox / Relativity of simultaneity; Argumentation ethics
   --follows--> Homesteading — arrows correct, only the words were
   swapped. docs/architecture.md wording fixed to match.

2. Session-local terminology leaked into node names ('Criterion C',
   'Action axiom (F1)'). Both extractor prompts gain a naming
   doctrine: names use established, discipline-canonical terminology
   (glossary-headword test); never source-local bookkeeping (letters,
   numbering, positional labels); local shorthand goes to aliases.

3. Near-duplicate propositions and lonely nodes (~40% of fresh-run
   nodes edge-less; 'Argumentation ethics' vs 'A priori of
   argumentation' duplicated despite the auditor having linked that
   phrase once). Root cause is upstream of the auditor: the extractor
   had no self-merge or edge-completeness duty. Both prompts gain:
   MERGE YOUR OWN OUTPUT (same claim at different precision = one
   proposition, phrasings to aliases; nothing downstream dedupes) and
   edge COMPLETENESS (a proposition that arose as evidence for,
   consequence of, or prerequisite of another must carry the edge —
   a graph of lonely nodes is a failed extraction).

Owner rulings on the open question: keep two edge types (no
'relates to' junk drawer); re-measure loneliness after these fixes.

Gates: typecheck, 1059/1059, lint, build.
2026-09-28 17:48:51 +03:00
amosblomqvist 1b50013551 extraction cleanup: events ledger dies, markdown source view, auditor-as-linker (2026-09-27 rulings)
The graph becomes raw reasoning chains only. Quiz outcomes and
understanding observations are no longer formalized; assessment
moves to the future practice module.

Events ledger removed end-to-end: LedgerEvent/LedgerEventKind/
UnderstandingLevel/ExtractionAttribution/AuditResult.dropped and
snapshot nodeObservations deleted from contracts; quizEvent builder
and the events step of mergeFindings gone; appendEvent/readEvents
dropped from the port and store; LEDGER_FILES loses events.jsonl
with a one-time purge of a stale file next to the nodes ledger;
graph-deletion and the delete dialog lose event purge + quiz-answer
impact counts; GraphSessionProposition becomes a pure birth list.

Whole-session markdown source view: new core/session-markdown.ts is
the shared formatting engine (id-anchored speaker blocks, quiz/
recall/question blocks, tool one-liners) — the extractor's prompt
view and the future user-facing export render from one engine.
Coverage fingerprints deliberately stay on the old line format
(content-hash semantics; re-formatting never re-opens covered
history). The whole session goes to the extractor — no phase cut.

Auditor refocused as the identity linker: never sees the source;
explores the graph via new auditor-specific tools in
agent/tools/graph.ts (find — glob over name/statement/aliases;
list_sessions; list_propositions with full statement text; no
timestamps) instead of an injected full-graph dump; equivalence
bias — paraphrases merge, only genuinely distinct propositions stay
apart; no drop channel, the extractor is fully trusted; may draw
bridging edges (typed follows/supports) between existing nodes and
the batch, resolved by the merger with chat-scoped message-less
provenance. Agentic loop, maxTurns 16.

UI: the node detail card gains an edges section above the sessions
list (follows-from / supported-by / what-follows / what-it-supports,
rendered only when present, click-to-select neighbors); the
understanding-note chips are gone.

Prober list_propositions reworded to what a session covered —
names only, no levels. docs/architecture.md + README updated to
the as-built pipeline. Gates: typecheck, 1059/1059, lint, build.
2026-09-28 16:54:44 +03:00
amosblomqvist cd09c12b56 vision docs: practice module, side chats, branching, parked persistent agent (2026-09-27 ideation round)
Four living documents capturing the post-explain ideation:

- vision-practice.md (priority 1): exams over chosen node sets; assessment
  record model (extractor observation = data point zero, practice appends,
  labels-for-extractor/numbers-for-everyone-else, aggregation TBD-Bayesian);
  problem-engineer agent flow with self-verifiable coverage metadata and
  commit-before-pose grading via a dedicated write-seam tool; formats
  configurable, theory pending from owner.
- vision-side-chat.md (priority 2): per-block hover-revealed mini threads,
  invisible to the teaching record; v1 = block-only context, escalation
  ladder documented; fork idea set aside (role confusion).
- vision-branching.md (unscheduled): tree-shaped records, plan-overlay UI;
  pi SDK has no native branching (verified) — our own record-model work.
- vision-persistent-agent.md (PARKED): markdown session records as the
  pull-based substrate, graph-as-truth over user.md, graph composer as
  interface, session kinds/three-surfaces note.
2026-09-27 23:12:12 +03:00
amosblomqvist d6d7c09e2e quiz nodeId required (owner ruling 2026-09-26): no untagged teach quizzes — boundary rejects, model retries tagged; off-map checks route to ask_user_question; anti-rush doctrine in schema+prompt; docs+comments updated 2026-09-27 02:05:51 +03:00
amosblomqvist 145b470752 fix: verdict death — plan→teach seam inferred from first nodeId-tagged quiz
The Accept/Decline verdict gate is gone (owner ruling 2026-09-26):
presenting the plan ends the teacher's turn; the learner's next message
is a natural reply. Teaching begins at the first quiz call tagged with a
nodeId (probe quizzes carry none) — both folds (view phaseAt3, native
phaseAt3Lh) advance on the call itself, and the teach divider renders
before that whole assistant block. No backwards compat: verdict markers,
the verdict input event, optimistic appends, composer buttons, and
projection all deleted; prompts and docs rewritten to the natural flow.
2026-09-27 00:48:46 +03:00
amosblomqvist ad0a6e5201 feat: Wave C pivot - steer markers, not follow-ups
Owner correction (2026-09-26, later): a real lesson is ONE continuous
teach->quiz->teach run - there are no run-would-stop moments mid-lesson,
and the run ends that DO exist are quiz stopper exits where delivery
must not fire. Subagent results must land BETWEEN messages INSIDE the
run, never splitting a toolCall from its toolResult.

Vehicle 1 now hands markers to agent.steer() - pi's STEERING queue:
the loop drains it at TURN BOUNDARIES (after a completed assistant
message AND its tool results - the bonded pair survives injection) and
again at RUN START (a continuation drains before its first LLM call),
then CONTINUES the run with the marker in context.
steeringMode "all" replaces followUpMode.

registerLiveAgent moved BEFORE runPiTurn and now hands pending markers
itself: the start poll drains the queue SYNCHRONOUSLY inside the
runPiTurn call, so a later registration would miss it. A marker
stranded by a quiz-terminated run therefore rides the ANSWER run's
first LLM call - the native replacement for the old continuation
coalescing.

Settlement reconciliation, the idle wake, probe-handoff exclusion,
and the registry-as-truth design are UNCHANGED; stranding is now
exactly the stopper-exit case.

Tests: jobs 45/45 (fakeAgent.steer; new pin - registration steers
pending markers into the new run, parked wake never fires); chat-flow
24/24 (new e2e through the real composition - a gated mid-run turn, a
marker delivered while the turn streams, record shape
[call, result, MARKER, response] with the marker adjacent to the
completed unit, the model SEES the marker in its next prompt, zero
wakes); full suite 999/999, tsc/lint/build green.

Live smoke (PORT=3004): research dispatched in the probe turn parked
behind the dangling quiz; on the quiz answer the marker injected at
the answer run's START poll - record [answer result, marker, teacher
response], job flipped done/delivered at settlement, brief card
rendered with its sources toggle.

Plan doc: docs/PI-NATIVE-MIGRATION-PLAN.md gains the pivot section.
Deferred (owner list): thinking blocks rendering after messages;
vanishing tool-call output tokens.
2026-09-26 04:31:07 +03:00
amosblomqvist 7f3d8aadcc docs: Wave C planning — pi-native follow-up delivery (forks resolved) 2026-09-26 03:15:14 +03:00
amosblomqvist 7c0e8d9a50 docs: Wave D planning — native wire + client rewrite (forks resolved) 2026-09-26 01:15:25 +03:00
amosblomqvist b3ebbad040 docs: track the pi-native migration plan (recon + Waves A/B records)
Owner-approved home: docs/ instead of the repo root. Wave C (stateful
agents, seam 5) stays optional and deferred; Wave D (SSE dialect +
client rewrite) is next.
2026-09-26 00:57:24 +03:00
amosblomqvist 085fdc1a98 Payload slimming, pre-signing half: the asar stops shipping the world
The measured problem (330MB DMG / 1066MB app): electron-builder's
dependency collector packed the whole production dep tree into
app.asar (502MB) regardless of the files allow-list, because
normalizeFiles turns the array into one {filter} fileSet whose
entries ALL feed the per-dep copier (its implicit '**/*' passing
everything), and unpacked the native rungs into a 150MB
app.asar.unpacked. The launcher needs nothing but electron + node
builtins — the server ships via extraResources.

The fix (electron-builder.yml files): gitignore-style negation
ordering — '!**/*' first excludes everything, then only
electron/** and package.json re-include. The same list reaches the
per-dep filter, where app-builder-lib's destination-path quirk
(stat.moduleFullFilePath in util/filter.js) means no dep file can
ever re-include: all 566 modules collect zero files (DEBUG-mode
verified). Locale trim rides along (mac.electronLanguages: en).

Result on a fresh desktop:dist: app.asar 502MB -> 16KB (shell only:
electron/main.cjs, preload.cjs, package.json), app.asar.unpacked
gone, Frameworks 288MB -> 239MB, app 1066MB -> 367MB, DMG 330MB ->
144MB. Resources/server curation untouched (127MB, sharp native +
pdfjs traced and verified live).

Live verification against the packaged app: server spawn via
ELECTRON_RUN_AS_NODE on its kernel-assigned port, model slots via
PUT /api/settings/models, chat round-trip streaming an exact echo,
read tool on a PDF through the pdfjs path (exact text extracted),
image attach through the vision slot, GET /api/graph snapshot and
render-settings.

Docs full-accuracy pass (owner-approved scope): README +
docs/architecture.md swept of the dead embeddings / graph-context
injection / cluster-namer / semantic-clusters story (wave-7
casualties), MODELS/config roster rewritten to the BYOK slot
chain, EXTRACTION.chunkTokens -> graph.extractionChunkTokens,
route count 16 -> 25, steer_ctx_ dropped, seam table re-pointed at
the prober graph tools; docs/research/stt.md:50's "we already ship
transformers.js" premise corrected; config.ts orphaned EXTRACTION
doc block (left over from eed4b4e) deleted.

Note for master: npm prune still owed in the master tree (~690MB
stale @huggingface/onnxruntime trees absent from the lockfile);
worktree tree is clean and verified.
2026-09-24 20:50:52 +03:00
amosblomqvist 401a21e83f teaching methodology: theory doc + step-unit teaching loop + minimal-tree planning + click-as-collapse philosophy (owner theory, 2026-09-24) 2026-09-24 18:40:15 +03:00
amosblomqvist 8d4c219f0c docs: probe-teach-split — the plan-acceptance data part is data-verdict (as the code has always sent); flagged during wave 1 2026-09-19 13:17:15 +03:00
amosblomqvist 487f5d4cea wave 1 (platform stream): background-observability accuracy + owner-driven follow-ups
- observability: liveness-gated stale-job recovery, extraction run registry on globalThis with
  explicit 'embedding' phase, /api/jobs force-dynamic, client refresh nudges at every seam
- headless: providerSafePng canonicalizer (multi-IDAT Chrome PNGs → single-IDAT) fixing qwen 400s
- composer: Enter routes through pure enterAction — accepts a pending plan verdict (owner decision)
- markdown chrome: session-scoped mermaid overlay + md-blocks component set + pan-zoom math,
  corner-copy (clipboard grant in electron main), math/table/code block fixes, heading weight 900

543/543 tests; typecheck/lint/build clean
2026-09-19 13:16:34 +03:00