Preserve original contributor commit 3bb28e5f29. Resolve only the settings-dialog conflict by retaining current General/update behavior: the donor General-only language placement is explicitly superseded by the human-approved row BELOW the full-width Deepgram key. The assigned implementation worker will implement that placement and approved shared dropdown finishing. Foundation only, not a checked candidate.
Adapt the bounded probe-completion foundation from PR #130 by aksalatdev (source commit 535202bebe038fa1e0648f5df809d5ccfc26d071), not its mixed stream-salvage implementation.
Require a non-failed call followed by a matching probe_done result with neither isError nor explicit details.ok:false. Preserve older clean acknowledgments, saved attempts, and all existing handoff/retry/provider policies. Share the native completion seam with phase detection and exclude explicit refusals from UI phases.
Verify retained failed-probe Retry recovery with real agent/tool composition and synthetic engines: one fresh conclusion, one handoff, then teacher selection, without stored-tool replay. Focused tests 58/58; full tests 2473/2473; typecheck and full ESLint pass in isolated storage with scrubbed provider credentials.
Co-authored-by: aksalatdev <73862605+aksalatdev@users.noreply.github.com>
The hand-packed icns was pixel-clean (verified by unpacking and scanning
every size), but macOS 26 composites legacy .icns icons into the Liquid
Glass system at render time; stacked on Icon Composer's baked PNG-export
lighting, that clips the rim highlights into the near-corner specks and
abrupt highlight ends (owner report 2026-10-13). The fix is the real
layered icon: mac.icon now points at the Icon Composer document
(build/icon.icon, copied from ~/Documents/Affinity/lh-logo) and
electron-builder compiles it via actool into Assets.car — macOS 26+
renders the true Liquid Glass design (dark/tinted variants included).
actool's backwards-compat icns caps at 256px, so scripts/after-pack.cjs
swaps in the existing full-res build/icon.icns before signing: pre-Tahoe
macOS keeps today's quality, Tahoe gets the layered icon.
Windows/Linux get the owner's flat design (rounded squircle with baked
transparency): scripts/make-icons.mjs renders build/icon-flat.svg via
resvg into build/icon.ico (16→256 PNG entries) + build/icon.png (512),
and electron-builder.yml pins win.icon/linux.icon.
desktop:dist injects DEVELOPER_DIR (actool needs Xcode; the machine
selects CommandLineTools by default, CI's setup-xcode takes precedence
via the :- default). The release workflow's mac job selects Xcode 26+
explicitly. Verified locally: mac --dir build (Assets.car + checksummed
full-res fallback + CFBundleIconName/IconFile) and the win-unpacked exe
carries our ico bytes; NSIS/AppImage assembly is CI's (cross-build
tooling can't run on arm64 macOS). Runbook updated. 1610/1610.
- switch the math authoring/rendering contract from \(...\)/\[...\] to the
standard $...$ inline / $$...$$ display: prompts (20-question-tools,
40-formatting), singleDollarTextMath on the renderer, legacy reader kept,
and escape-debris recovery extended to dollar spans (mirroring micromark
delimiter-run rules)
- exact answer membership now precedes debris cleanup in
coerceCorrectAnswer (calculus incident: an exact sentence-final-period
label was stripped as model debris, then rejected); option keys are
passed at the boundary/grading seams, the dock no longer pre-coerces
- a complete, phase-correct JSON-escaping example rides the probe/quiz
tool descriptions (serialized from one canonical body; quiz leads with
type+nodeId), plus the one-sentence JSON-escaping rule in 40-formatting
- ask option descriptions render markdown/LaTeX in the pending dock;
settled cards keep the details line and option descriptions
retroactively, read from the persisted call arguments (chat passes
part.input to AnsweredCard)
1566/1566 tests on both navigator configs; typecheck, lint, production
build, standalone packaging, and Electron-runtime smoke all green.
Chat records gain version (CURRENT_CHAT_VERSION), stamped by a single
writeChat choke point in the store; absence reads as v1 — existing files
migrate on their next write, no mass rewrite. The graph family (four JSONL
ledgers, sources.json, render-settings.json) shares one lazy sidecar,
graph/format.json; positions stays unstamped (rebuildable) and jobs stay
unstamped (transient).
Downgrade scenario ruled honest-refusal: records stamped newer than the
running build read as absent (chats, logged) or throw (graph family) — a
read-modify-write store never rewrites what it doesn't understand.
Golden fixtures stay the unstamped legacy shape on purpose (lazy v1 is
current-valid by design); the stamped write path is pinned by
tests/schema-stamps.test.ts. Reads never write — the golden byte-identical
pin holds.
Owner-manufactured teach master (chat_mv27rrra: compaction, subagent-abort,
every teach tool) + practice lifecycle (chat_mv293wv5: the full marker cycle
including the first-ever problem-dismissed), the real graph ledgers, five
jobs across the kind x status matrix, and settings. tests/golden-fixtures
reads all of it through the stores via the LH_DATA_DIR seam and pins the
fixture law: real data parses, flattens to a legal LLM wire, derives the
graph, covers every custom-marker kind, and reads never write.
Fixture law: an intentional format change updates these files in the same
commit; an unintentional one fails the suite.
The README becomes the storefront for the public repo — what it is, why
it exists, BYOK/local/inspectable values, download, quick start —
written from the canonical articulation (owner doc, 2026-10-05): the
dependency structure of understanding, measure-first teaching, one
reasoning step at a time, the accumulating world model. All developer
content (commands, env, architecture, mechanisms, testing, lineage)
moves verbatim to docs/development.md (stale suite count and a private
machine path dropped on the way).
Owner will edit before the release; this is the base coat.
- LICENSE: AGPL-3.0-only (holder Amos Blomqvist), package.json gains "license".
- Product/package name unified: package.json "name" is now "learnharness"
(matches the GitHub repo); electron-builder productName/appId already
were LearnHarness/com.learnharness.LearnHarness.
- Deterministic userData: app.setName("LearnHarness") early in main.cjs
— Application Support dir is exactly "LearnHarness" in dev and
packaged runs (packaged Electron would otherwise derive it from the
package name). Dev shell becomes "LearnHarness-dev". Pre-release
rename: zero external users, no migration code; the local test dir
was moved by hand.
- docs/ diet: nine vision-*.md splinters, the completed pi-ai migration
plan, and research/ notes leave the tree (retained privately in the
git-ignored memory; full history stays in git). Public docs remain:
architecture, vision, plan, probe-teach-split, pi-engine-swap,
keyboard, teaching-methodology (+ release-0.3 until the ship).
Gates: 1489/1489, typecheck, lint.
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.
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).
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).
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.
- 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