40dc1b7f85ff2de1895bb2fd432bf25825d3f7c3
20
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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. |
||
|
|
27c5d9df19 |
practice slice 4 — pipeline, registry, API, feed
src/agent/subagents/practice-pipeline.ts (sibling of practice.ts —
orchestration vs calls): createPracticePipeline(graphStore+practiceStore
+chatStore+PracticeModels). Fire-and-forget front doors returning
{set, done}: createProblemSet (refuse → resolve selection (chatId =
ANY-provenance membership per brief; nodeIds raw) → chains → snapshot →
createSet partitioning → registry row → runPartitioner → stubs land
(generating) → ONE generator per problem behind the process-wide
3-permit abort-aware semaphore (__lhPracticeSlots, extraction idiom) →
per-problem failure isolation → ready (errored problems included);
abortPractice (idempotent: controllers + 'Aborted' marks; dropped
grades keep answers, re-submittable); submitAnswers (ready-only,
already-graded rejects, one grading job per answer under the same
semaphore); deleteProblemSet (abort-first). Per-set write mutex on
globalThis (__lhPracticeLocks, audit-lock idiom); registry
__lhPracticeRuns one row per set per kind; boot sweep marks orphaned
partitioning/generating sets 'Interrupted (server restarted)'. Typed
errors: EmptySelection/UnknownChat/SetNotFound/SetNotReady/BadAnswers.
Usage folds stage logs into the registry row (v1: completion-folded).
src/server/practice.ts: thin service — strict params parsing, typed
error → response mapping (503 envelope family, 400/404/409), list/get.
Routes: POST+GET /api/practice/sets (202 {id}), GET+DELETE
/api/practice/sets/[id], POST /api/practice/sets/[id]/answers (202).
Composition root: chat-handler wires createPracticePipeline (engineFor
fresh + providerKeyMissing gate) exported as ; barrel re-
exports. Feed: observability activityFeed() += practice rows
(ActivityPractice); dock blobs (process 'practice', GraduationCap
icon, set-keyed abort); /api/activity/abort kind 'practice'.
tests/practice-pipeline.test.ts — 13 tests: end-to-end (stubs mid-
flight via gated generators, coverage complete), failure isolation,
3-permit semaphore pin, abort mid-generation, registry shape pins,
refusal-before-create, chatId any-provenance selection, typed errors,
answers→grades land + graded rejects, boot sweep (orphan + live-row),
delete. 1114/1114, tsc 0, lint 0, build 0.
|
||
|
|
f72a3f3ada |
Activity dock: unique row ids for per-chunk extraction jobs (fix duplicate React keys)
The live import surfaced it: a multi-chunk notes source renders N feed rows, and the dock keyed every one by run.chatId (the SOURCE id) — React's duplicate-key error. The activity feed also dropped run.part entirely, so all chunk rows showed identical titles. - ActivityExtraction/FeedExtraction gain a unique row id (source id, or 'src#i' for chunk jobs) and part; the feed title carries the '· i/N' suffix like the graph status route - toBlobs keys extraction blobs by the row id; chatId stays the SOURCE id (identity for links) - abort sends the SOURCE id for extractions — aborting any chunk row kills the source's whole fan-out (the new source-wide abort), and the abort route's id charset rejects '#' anyway - new pin: one source, two chunk rows → two blobs, distinct ids, same chatId; dock fixtures updated |
||
|
|
1f777f770c |
Zero-keys wave: provider-key gate renders honestly everywhere
Over Platform's 66dbea9 key-presence seam, the five owner-locked tasks: 1. Zero-keys pill — components/setup-cta.tsx: pure hasProviderKey (classifyKeys reuse — tool keys like Deepgram never count), useZeroKeys (GET /api/keys, re-read on the new lh:settings-saved 'keys' signal), ZeroKeysPill (testid setup-cta-keys) taking PRECEDENCE over the models CTA on chat/home/new-session. SettingsSection gains 'keys'; keys-section dispatches after save AND delete. 2. Setup stage 1 is self-contained: provider picker (MenuShell + ProviderMenuList) + key paste + Save via PUT /api/keys/:id — the dialog STAYS MOUNTED; its own save dispatches the keys signal, the fetch effect re-runs on it, and the stage flips to the pickers on its own. onOpenKeys + setup-open-keys die. SetupKeysBody exported for SSR pins. 3. parseProviderKeyRefusal (chat.tsx, envelope-verbatim twin of parseModelsRefusal) joins the revert-draft-and-open-setup interception; activity-dock reads 'needs a provider key'; dispatch-card shows 'Add a key' — both deep-link setup, which lands on stage 1 by itself. 4. Dictation trio: the token route's only 503 (missing Deepgram key) becomes the client-authored DICTATION_KEYLESS short line; the errored idle click clears the error and deep-links Settings -> Keys -> DEEPGRAM_API_KEY via the new open(section, row) seam (dialog landOnRow -> SettingsDialogBody initialRow -> KeysSection openRow follow-effect). Live-verified on :3001 under LH_IGNORE_ENV_KEYS=1 (the fresh-user switch): pill on all three surfaces + precedence over the CTA, stage-1 save -> auto-advance with dialog mounted, real 503 send refusal reverting the draft and opening setup, dictation 503 -> short line -> Settings lands on the unfolded Deepgram row with the error cleared, keys-section save and delete both flipping the pill live, and a fabricated refused research job rendering the 'Add a key' dispatch card. 926/926, tsc/lint/build green. |
||
|
|
81709afd22 |
Stats-bar catalog join + dock numerator consistency (context-meter close-out)
Platform's context-meter pair (04e336d, 18cc493) left two client seams in my band; this closes the arc: 1. STATS-BAR CATALOG JOIN: contextWindow's denominator resolves ONLY from pi's live catalog — stats-bar now builds the provider:id -> contextWindow index client-side (catalogLimits in model-picker.tsx, the client twin of the server's catalogContextLimits join) over the picker's existing /api/models fetch/cache (useModelCatalog exported; module-level dedupe, one fetch feeds both ModelControl and the meter). Catalog pending or failed -> undefined index -> limit null -> USAGE-ONLY (Platform's interim render IS the null path; the honest-render precedent, never a fake window). 2. DOCK NUMERATOR CONSISTENCY: toBlobs' context fold was still last-step input+output only — now the same cache-inclusive footprint the session meter uses (all four directions; provider "input" EXCLUDES cacheRead/cacheWrite, so the old fold read wildly low on cache-heavy jobs). Same metric everywhere. Review of Platform's interim edits in this band: verified as-shipped — stats-bar's usage-only render (summary + popover), FeedJob.contextLimit with the line-hides null treatment, and the render-test rewrites all fit. Only fix needed: none beyond the above. Tests: catalogLimits pinned pure (index shape, provider:id keying, missing-model absence); toBlobs pinned on the cache-inclusive numerator (10k+0.5k+120k+30k = 160.5k against a 200k feed limit). 889/889. Live-verified on :3001: seedplan's bar reads "18.7k / 1M" (footprint 18725, catalog 1048576); a nope/unknown-model chat renders "12.8k" usage-only with cost "-"; the popover detail matches; the dock blob's context line renders "946 / 1M" from a server-joined feed (an unintentionally real runner-adopted job — the full chain, live). |
||
|
|
4ded547cb5 |
Context-meter amendment: CONTEXT_LIMITS dies — pi's live catalog is the only limit source
Owner ruling (2026-09-23, amendment to 04e336d): no hardcoded fallback exists anywhere. contextWindow(record, limits) resolves the denominator ONLY from the live index (provider:id -> contextWindow straight from pi's catalog); a model missing from it (historical session, package update, failed fetch) reads limit null and the meter renders usage-only — the honest-render precedent, never a fake window. - src/core/prices.ts: the 1354-entry CONTEXT_LIMITS table (65KB) and its generation apparatus deleted; the fold returns limit: number | null. PRICES untouched (regen verified byte-identical via the reworked sync-pricing script). - scripts/sync-pricing.ts: generates PRICES only; contextWindow leaves the priced map, the guard, and the template. - The dock meter's owner-selected treatment: the activity feed carries the limit server-side. catalogContextLimits() (server/model-catalog.ts) builds the index over the same keyed providers the picker federates; activityFeed() joins it onto every running job (ActivityJob.contextLimit, null when the model is missing — the dock reads it off the feed, no client-side table, no regression while the UI's catalog-join chunk lands). - components/activity-dock.tsx: toBlobs reads job.contextLimit (import of CONTEXT_LIMITS gone); stats-bar renders usage-only on null limit (mechanical interim — UI stream's join chunk follows). - tests: usage.test.ts pins the no-fallback contract (index hit / absent / empty / missing-model -> null); activity-feed.test.ts pins the server join (catalog model -> 1048576, unknown -> null, no-usage -> null); render pins updated to the interim usage-only render. |
||
|
|
7a17230f26 |
Wave 7 close-out: the graphTools surface, wrapped cluster labels, the orphan sweep
Part 1 — the surface:
- Settings ▸ Agent ▸ Tools gains the prober GRAPH-TOOLS row as its FIRST
row (owner ruling: in the Tools section, not the tool-toggles list —
its subject is the map, its mechanism is agent capability). Writes
agent.graphTools {enabled} (absent = on, the toggle discipline) via
the section PUT; composeGraphTools pure seam. Its own key in the agent
contract — never in the tools map.
- The row carries the search-row hint discipline: "Connects prior
sessions' knowledge into new ones — the prober reads the knowledge
map" (owner wording). The honest interplay composes both: when
graph.enabled is off the row dims + disables with a "Graph is
disabled" hint (the effective gate itself is server-side, per-turn
fresh). AgentSection reads the graph section (record.graph, passed by
the dialog) — read-only.
- Cluster labels wrap: wave 7 made labels session names and FILE PATHS,
which the old single-line fillText could not bound. wrapRegionLabel
(pure, exported): word-wrap first, char-split long paths (the
node-label discipline), capped at 3 lines, overflow folded into an
ellipsized last line (owner: wrap, cap 2–3, ellipsize). Pre-wrapped
once per geometry build at the SCREEN-fixed size (viewer-relative
labels — the wrap never goes stale mid-camera-move), drawn centered
line-by-line at the centroid. Deleted-session clusters keep their
hull-only honest silence (label null → no lines).
- The orphan sweep: StatsBar's leftSlot prop is dead (its one consumer,
the knowledge-context slider, died in c690181) — removed, and pre-chat
views now render NOTHING on the left (owner ruling this round: a
"— · —" summary over an empty popover is pure noise) — the dictation
middle just widens. Stale knowledge-context comments swept in
chat.tsx (adoption-window + activity-nudge docs), activity-dock.tsx
(useActivity ground rules), dictation.tsx (×2 adaptive-meter docs),
home.tsx (pre-chat dock comment).
Part 2 — Teacher's c690181 review: clean. Imports, KIND_ICON/KIND_LABEL
entries, render branches and event seams all excised whole; the probe-
handoff branch re-indented intact and merge-adopted; tests updated in
lockstep (merge-adopted, record-adapter, render-new-session retired).
Platform's 4b60e0f roster-string fixes (labeler dropped from both
SLOT_META blocks) reviewed — consistent across agent-section +
setup-dialog.
Gates: tsc clean, 854/854 (8 new: wrapRegionLabel cases, graphTools
SSR rows ×3; stats-bar + ladder fixtures updated to the new rulings),
lint 0, build ok. Live on :3001: graphTools row first-in-group with
hint, off→{enabled:false}→on round-trip, graph-off dimming + hint +
disabled toggle, pre-chat rows summary-free, chat view keeps its
summary, long-title cluster label wrapped without console errors
(visual read unavailable this session — pure wrap is unit-pinned).
|
||
|
|
6b13583e96 |
wave 7: the prober reads its map — graph-context injection dies whole
The owner's zero-configuration ruling (2026-09-23): nothing configures the graph beyond chat models. The prior-knowledge injection — its budget dial, its runner, its marker part, its dock/dispatch entries — is removed across all bands; the prober now reads the learner's persistent knowledge map on demand through two gated tools instead: - list_sessions: every birth session, names + kinds + counts + dates (graphSessionIndex, Platform's core seam) - list_propositions(session): what was born there + the session's own latest understanding level + that evaluation's date (graphSessionPropositions) — names/levels/dating ONLY, no statement text (owner ruling: the map's organization is the prober's business, its prose is not) Registered via createProberTools when chat-handler passes graphStore (the graphToolsEnabled gate decided server-side, per-turn fresh); the prober doctrine gains the map-review nudge at session start. Also: runner's JobKind special-casing collapses (KIND_ROLE/KIND_RUNNERS are plain records again), semantic-clusters.ts dies with its test, the embedder dep's doc tells the wave-7 stub story, and ports.ts's readClusterLabels/readNamingRuns surface stays untouched for Platform's ledger-port interlude. 848/848 tests, typecheck/lint/build green. |
||
|
|
5ab52f07f6 |
BYOK universal gate rendered honestly: mid-chat setup flow, refusal-aware cards
- existing sessions: a send that lands the 503 models_not_configured envelope renders as the SETUP FLOW, never the error banner — the record refetch drops the refused orphan (the gate refuses before the write-ahead persist), the draft goes home to the composer, and Save auto-replays it (owner ruling); parseModelsRefusal reads the envelope from the transport's verbatim error text (exported pure, tested) - persistent 'Set up your models' CTA above the chat composer while slotsConfigured is false (server-read seed via app/chat/[id]/page.tsx) — legacy per-chat overrides may still send with the pill present; the server gate stays the truth - SetupDialog prefill + 'needed' marks (owner ruling): pinned tiers open with their pick (matched in the federated catalog — lapsed keys don't prefill), unpinned tiers carry the mark; Save still requires all three; prefillFromSlots exported pure, tested - refused jobs read as INFO: dispatch card shows a quiet 'needs model setup' line + 'Set up models' deep-link (ChatActionsContext.openSetup); dock blobs' activity reads 'needs model setup' instead of 'failed'; real failures keep the red tone; isModelsRefusal added to the envelope's home module - agent-section ModelsBody.slots type honest to the nullable contract |
||
|
|
9a90dd51d9 |
feat(T2b): sources v0 — read tool + pdf-reader sub-agent
Learner source files, on the teacher's own terms (nothing auto-injects): - read tool (src/agent/tools/read.ts): pi-read port — path/offset/limit, 2000-line/50KB truncation with use-offset continuation notices, image files (jpg/png/gif/webp/bmp) returned as native image content parts (new LhToolImageResult escape hatch in lh-tool.ts). Always-on in both the prober and teacher registries (user-decided 2026-09). - pdf-reader sub-agent (JobKind 'pdf', toggleable, ack-and-steer): triage via pdf_info (pdfjs text layer: per-page text/image/math-density), targeted pdf_extract, regex pdf_search (hits count as consultation), and pdf_render — pages painted by pdfjs onto a canvas shim in headless Chrome (the pdfium viewer doesn't paint headless), screenshot fed back image-in-the-loop (visual-maker idiom, providerSafePng applied). The unread-finish gate bounces a zero-consultation brief once. Teacher gets a text brief with page citations only; renders never leave the loop. - MODELS.pdfReader = qwen/qwen3.8-flash (user override 2026-09-19: the look-at-renders workload wants the vision-proven model; catalog listed glm-5.3-flash as image-capable but qwen is the known-good daily driver). - UI: PdfCompleteBadge + PdfBriefCard (steer-card, job-card), dispatch card + activity-dock kind entries (type-forced, flagged for UI review). - Prompts: 'Learner source files' section in teacher/30-jobs.md; read line in prober/10-probe-doctrine.md. Doctrine rides tool descriptions per the self-contained-tools principle. |
||
|
|
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 |
||
|
|
cbecb8df03 | ui: rename learner-facing 'knowledge context' copy to 'graph context' (components + render tests) | ||
|
|
2a8554af27 |
Typography round 4 (owner): the snap to the ladder — prose 16.5→16, xxs rung merged into xs
The system: the ladder is the truth, everything snaps to it. - Prose snaps to the ladder's 16px rung (--lh-prose-size 16.5→16; the half-pixel was a reskin-era hand-tune with no visible payoff and a system cost). lh-prose, lh-input, and the session titles all read the one variable — one reading size, now a ladder citizen. @theme inline inlines literals without emitting runtime vars, so the variable can't var()-link to --text-lg: documented as the one deliberate duplication (change one, change both). - The 10px rung merges into 11px: one pixel apart was a distinction with no visible difference, and 10px sits at the mono legibility floor — quiet labels (Sessions, Background agents, dock hints, model-picker footers) get their quiet from color + tracking, not a notch of size. All 13 text-xxs uses → text-xs; --text-xxs token removed. - Derived constant follows: TEXT_CAP_SCALE 16.5/12.5 → 16/12.5 (1.32 → 1.28; test pin 232.32px → 225.28px) — the plan-graph text cap now tracks the snapped prose size. Live: title 16px, Sessions/Background-agents labels 11px, composer 16px with the 20px lh-input line box. Gates: typecheck, lint, 469/469. Lane note: components/graph-context-marker.tsx (graph-stream file) had a mechanical text-xxs → text-xs swap — required: the token removal otherwise leaves a dead class in their file. |
||
|
|
31978f3fc3 |
fix: background-delivery ground rules — the dock is the source of truth, delivered content shows the moment it persists
The owner's three-part test of the context-injection flow surfaced
timing/visibility bugs, fixed as standing ground rules for ALL background
processes (owner rules, 2026-09-15):
1. THE JOB STARTS AT SEND. triggerGraphContext moves from onFinish to
right after the write-ahead persist in handleChatRequest: its only
input is the first typed message (the topic), just made durable — so
embedding + cosine scan run CONCURRENT with the prober's turn and
the dock blob appears the moment the session starts, not after turn
one finishes. Extraction stays at onFinish (it needs answers
accumulated DURING the turn).
2. DELIVERED CONTENT SHOWS MID-TURN. Steers inserted server-side
(claimed at turn start, persisted write-ahead) used to wait for the
next turn's completion to appear — the full record sync mid-stream
is forbidden (it breaks the SDK's in-place continuation merge), so
adoption was deferred to idle. New append-only seam: core
mergeAdopted() inserts the record's new message ids in canonical
order (in-flight streaming tail kept last) and never rewrites a
local message; any other shape bails to the full-sync-at-idle net.
Wired at three points, no new standing polls:
- turn-start window: three bounded record fetches (400ms/1s/2s)
after a send catch the claimed steer while the turn streams;
self-terminating, survives the submitted->streaming flip.
- jobs-poll settle: mid-turn settles adopt append-only NOW, the
owed full sync still runs at idle.
- wake-adoption poll: each tick adopts persisted wake content, so
steers show at the wake turn's START, not its finish.
The dock stays honest with zero gap: jobs hold "running" while
queued behind a pending question ("delivering context"), then flip
done at claim/delivery — visible from start to delivery, and the
turn-start nudge (useActivity().refresh) clears it within ~150ms.
3. THE MARKER IS CLICKABLE (observability). The knowledge-context
SystemLine becomes GraphContextMarker: quiet line, popover with the
delivered propositions IN DELIVERY ORDER (most similar first — the
order the budget filled), each with its folded understanding level;
Esc / outside pointerdown close. The part data was already the audit
trail — now it's readable at the seam where it landed.
Tests: mergeAdopted unit pins (6), marker render pins (3) — 468/468,
tsc, eslint, build all green; live-smoked end-to-end: blob at send,
marker at t+500ms mid-turn, popover order + levels, instant dock clear.
|
||
|
|
1d45d77303 |
feat: graph context injection — the session's prior knowledge, steered in at session start (vision-context-injection.md)
One shot per chat, fired at the first turn's onFinish: the session's topic (its only typed message) is embedded as an EmbeddingGemma retrieval query; the semantically closest graph propositions are selected under the session's token budget (pure budget, no similarity threshold — the decided doctrine) and delivered as a marker-only steer through the wake pipeline, queuing behind pending questions like every sub-agent result. - contracts: ChatSettings.graphContextTokens (default 1024, max 4096, step 128, 0 = off); GraphContextData + graphContextText; the graph-context JobKind + buildGraphContextSteer (id steer_ctx_<chatId>) - core/graph-context.ts: brute-force cosine top-K, greedy chars/4 fill under budget, most-recent UnderstandingLevel fold, count cap 64, parse-don't-cast at the marker seam - server: embedQuery (role-asymmetric query format, same warm singleton — vector-space parity by construction); the runner (startGraphContext- Injection one-shot gates; runGraphContextJob validates ledger parity); wake branches (chatIdOf/jobIdOf, finalizeJob done, recoverStaleJobs re-deliver or contextRespawn); abortJob kills context jobs SILENTLY (nothing was promised in the lesson); turn-service trigger seam - adapter: data-graph-context flattens to the canonical block (malformed parts have no model face) - UI: the budget slider (home dock + /chat/new draft, the session-data strip); the quiet SystemLine "~537 · 10 propositions"; dock kind "Knowledge context"; model-picker PUT now spreads settings (a model pick can no longer clear the sibling override) Live-smoked end to end on the real embedder + real GLM: seeded ledger (10 propositions), session on "modal logic: possible worlds and the necessity box" — job in the dock, queued behind the prober's question, delivered on the answer, SystemLine in the transcript, wake turn reads the block and continues the probe. Gates: tsc, lint, 459/459, build. |
||
|
|
bbee72f354 |
Background agents → sidebar section (owner): the retired floating dock's successor
The old activity dock (fixed bottom-right, every view) was a cross-session
widget parked inside the session view — the same boundary confusion that
got the plan window moved. The sidebar is the app's cross-session surface,
so the agents section lives at its bottom strip now (user-locked design):
- ALWAYS present: empty means empty — the header ('Background agents',
matching the Sessions label language) sits alone; nothing hides and
nothing decorates (the total-cost span only renders when populated).
- Populates and grows when processes run: same blobs (sub-agents +
extraction runs, one poll, inferred cost, elapsed heartbeat), capped by
max-h-[40dvh] + internal scroll so a pile never crowds the session list.
- Per-blob END button (unchanged abort contract); the detail popover
becomes an INLINE expansion — the section scrolls, a floating popover
would clip at its bounds.
- Sidebar-native skin: border-t separator matching the sidebar's edge,
session-row hover language, no floating-card chrome.
- Sidebar closed = the learner opted out; no ambient signal for now
(user decision — invisibility until a clearer observability story).
- Narrow-mode count badge/drawer machinery retired with the dock.
app/layout.tsx: ActivityDock mount removed, ActivityProvider stays (the
sidebar consumes the context). Live-verified: populated rows with inline
detail + session link, abort makes the row vanish, idle renders the
header alone, closed sidebar hides the section with nothing floating.
Gates: typecheck, lint, 423/423 tests.
|
||
|
|
3360181e41 |
Wave-5 reskin batch 5: normalize stray pixel text sizes onto the declared ladder
--text-xxs (10px) joins the ladder; every component-level text-[9|10|11px] literal in owned components now pulls from the ladder (graph-view.tsx and home.tsx untouched — graph stream). Cosmetic pins updated where touched. |
||
|
|
3e74f28e4f |
Dock END button speaks the real abort contract: already-gone is success, only true failures report
POST /api/activity/abort landed on main (
|
||
|
|
df58bb87f2 |
The stats bar: context + cost beneath the composer, per-turn detail
One quiet row beneath the composer/question shell (not flush to the screen bottom; static height, outside the morphing shell): - LEFT: context window + session cost, clickable → per-turn detail popover (context, total, unpriced count, one row per turn with model/steps/cost). All folds from core/prices.ts — sessionCost, contextWindow, turnCost — no cost math in components. - RIGHT: model + "Thinking —" — a placeholder slot for the coming model/thinking-level CONTROL (later work with its contract changes); display-only today, model read from the last turn's usage, pretty-printed (Glm 5.3 Flash). - Data: the record's turns log, seeded on mount, refreshed when the local stream settles, kept fresh by the existing record re-syncs. No turns yet → "—" everywhere; unpriceable turns never fold into a fake $0 (the doctrine). - formatUsd/formatTokens moved to components/usage-format.ts (shared with the activity dock); prettyModel joins them, pure + tested. |
||
|
|
e22f0bddb4 |
The activity dock: every background process, every view, bottom-right
One reserved corner of the shell, one blob per running BACKGROUND
process — session sub-agents (research/demo/visual) and global
extraction runs. Teaching turns are deliberately NOT here: the dock
exists to surface what the learner can't otherwise see; a running
teaching turn is already fully visible where it happens (user decision).
- Data: ONE poll of GET /api/activity (jobs across ALL chats,
title-joined, oldest-first + extraction runs), folded client-side
with core/prices.ts (jobCost). Adaptive interval — 2.5s while
anything runs, 10s idle, 4× slower on hidden tabs; a failed fetch
keeps the last feed (no flicker to empty on a blip).
- Per blob: kind icon, elapsed (ticking), cost, activity line, and an
explicit END on the blob's face. Click → detail popover (context
line, model, steps, open-session link). The context line is the LAST
model call's tokens vs the model's limit (JobUsage.lastStep — pending
agents-stream contract; "—" until it populates), NEVER a cumulative
sum. Absent data renders "—", never zero (the doctrine).
- Header: total running cost (sum of priced blobs, "—" when nothing
is priced). Narrow viewports collapse to a count badge → drawer.
- END posts to /api/activity/abort {kind, id} — the backend is being
composed by master over the streams' mechanisms; until it exists a
click locks the button and the popover says "Abort not available
yet" (never a dead control).
- Idle renders NOTHING. Mounted once in the root layout; every view
gets it for free.
toBlobs / formatElapsed / formatUsd / formatTokens are pure + tested;
SSR render tests pin blobs, the "—" doctrine, the face END, and the
priced total.
|