Commit Graph
82 Commits
Author SHA1 Message Date
bowenliang123 bf4cdd1e58 feat: add /context slash command with a centered context modal
Typing /context in the composer opens a centered dialog with the current
composition (provider-anchored headline, stacked bar, legend) and the
last-10-turn trend chart — the same pushed contextTimeline projection the
tab renders, no RPC, no polling.

The command is the plugin's own '/' trigger source, fully client-side:
no host descriptor, no session log records, nothing model-visible. The
composer token is consumed only when the modal closes (deferred
slash/input-consume-token with span / bare-token guards, fail-soft if the
draft changed meanwhile). Esc, backdrop click, and the close button all
close it and restore composer focus.

Also extracts the provider-anchored headline derivation shared by the tab
and the modal (headline.ts).
2026-08-16 22:26:46 +08:00
bowenliang123 6cb61a413b doc: move releasing part from README.md to AGENTS.md 2026-08-16 21:44:02 +08:00
bowenliang123 8ab7a9e8b7 update docs for DSH plugin's framework references 2026-08-16 20:55:23 +08:00
bowenliang123 aca38b24d7 feat(ui): dim non-hovered parts and emphasize the occupied frame
Composition-bar hover now recedes everything except the hovered part: the
container gets a lc-stacked-dim modifier whose unselected segments and the
free track drop to 35% opacity while the hovered segment/chip/free-track stays
at 100% (with the existing brightness boost). The occupied-region reference
frame is also stronger: 2px dashed label-primary at full opacity (plus a hair
inner ring) so it reads as an obvious dark frame against the colored
segments. Pure CSS + one container class — sheet and tests unchanged in
behavior (box/dim are hover-gated like before).
2026-08-16 09:10:38 +08:00
bowenliang123 d7b20a2978 feat(ui): frame the occupied region on composition-bar hover
The composition bar lays segments out against the FULL context window (width
= x / window) while legend/tooltip percentages are shares of the OCCUPIED
part (x / used) — so a slice that is half the used context reads ~20% wide
when the window still has headroom, which looks contradictory.

On hover (segment, legend chip, or the free track) a dashed frame now
outlines the occupied region (left edge to the used/window boundary), making
the 'share of used' reference visible; the frame renders only when a free
track exists (otherwise width already equals the percentage) and is
pointer-events:none. Segment tooltips now append 'of used context'
(zh: 占已用上下文) and legend chips carry the same hint as a title.

Adds the .lc-occupied-box style (shared --dsw-alias tokens), one i18n key per
locale, and render-test assertions that the frame appears on hover and
clears when hover leaves.
2026-08-16 04:14:30 +08:00
bowenliang123 38dbd2ad92 refactor(ui): move the context tab onto the harness's React stack
All view components are now tsx JSX function components instead of
hand-rolled h() factories; the bundle externalizes the framework seed words
(react, @deepseek-ai/dsh-client-ui-primitives) so the loader module table
resolves the single shared framework instance at runtime.

Reuses shared ui-primitives where a clean fit exists (injection and
model-switch event glyphs -> IconPlusOutline16/IconBranchOutline16); the
bespoke data-viz chrome (charts, shared-hover tooltip) intentionally stays
custom but continues to draw the shared --dsw-alias-* tokens.

esbuild uses the classic JSX transform, so the emitted createElement trees
are isomorphic to the previous h() calls and the element-parsing tests, the
chart-render assertions, and the real-React repro all stay green unchanged.
Adds @deepseek-ai/dsh-client-ui-primitives as a type devDep and documents the
framework-reuse findings in docs/redesign.md §8.
2026-08-16 03:51:00 +08:00
bowenliang123 6bebd495e9 feat: route context timeline through the harness session-projection pipeline
Replace the custom /dsh-context RPC channel + 2s client polling with a native
contextTimeline session-projection unit: the host registers one pure
init/apply/view unit on ctx.sessionProjections (framework-driven, persisted by
the projection cache, streamed via session/projection push frames), and the
client reads the finished value through the framework standard
useProjection seat. Deletes the client stale-while-revalidate cache and the
local service type stubs; grows the modern shelf registration (locale) and
adds the published @deepseek-ai/* types as devDeps plus zod as the single
runtime dependency (wire schema, kept external in the host bundle).

Fixes tool-result node attribution to read the durable source.callId /
content toolCallId instead of the never-present block.callId (tool labels had
silently never rendered).

Retention is tightened to trim by whole turn-runs deterministically (bounded
state, no 1200<->1500 oscillation under incremental folding).

Tests now drive the projection unit directly (host) and feed the view through
a useProjection stub (client); all chart/render assertions preserved. Adds
docs/redesign.md (review + target architecture, phases 0-3 landed) and
docs/CLIENT_SURFACE.md (client-side audit).
2026-08-16 03:25:01 +08:00
bowenliang123 926446ab7d docs: add AGENTS.md with agent guidance 2026-08-16 02:13:11 +08:00
bowenliang123 a50a8f1b19 chore: bump version to 0.8.0 2026-08-16 01:33:43 +08:00
bowenliang123 fc28869f04 fix(ui): context tab no longer inherits the chat bottom scroll
The context page scrolls inside the conversation shared page scroller
([data-conversation-scroll]); the chat auto-scrolls that container to the
bottom on every visit. The view own .lc-root height:100% never resolves
(the slot wrapper has auto height), so the context content flows in the
shared scroller: a fresh mount inherited the chat bottom position and
stayed locked there once the snapshot cache warmed up.

Own the scroller like the chat chatScroll ledger: keep a module-level
per-session position, restore it in a mount layout effect (first visits
start at the top), save it in an unmount layout cleanup before the incoming
view re-scrolls the container, and never re-apply on polls.
2026-08-16 01:32:48 +08:00
bowenliang123 97a2d9aa6c fix: align context occupancy with the official ring — provider-anchored totals
The context tab always showed less than the official chat context
indicator because the two sides used different rulers: the plugin
displayed its fixed-density heuristic total (4 chars/token, which
systematically undercounts CJK text and JSON schemas by ~10-15%),
while the official ring shows provider-reported billed tokens. The
running host also predated the occupancy feature entirely, so the
client fell back to the raw heuristic headline.

Core change: totals are anchored to the provider sample, heuristics
only supply the composition ratios (the official ContextMeter's own
trick):

- host: fold a contextPressure-equivalent occupancy projection
  (pressureTokens / sampledSurfaceTokens / occupancyWindow) and serve
  it on the snapshot; usage is sampled BEFORE the message joins the
  surface, and streamed assistant/chunk usage samples count too, so
  steps that fail mid-stream no longer fall back to pure heuristics.
- client: headline = occupancy.projectedTokens, with a last-request
  fallback deriving the same figure against older hosts (a page
  refresh fixes the live view before any server restart).
- composition bar, legend and trend bars are re-proportioned to the
  anchored total (ratios stay heuristic, values marked ≈); the
  conflicting 'estimate ≈ n (~p%)' second percentage is gone.

Verified by tests/diff-fold.mjs (kept as a differential regression
tool): the plugin's occupancy equals the official
contextPressure.projectedTokens to the token on all 33 real session
logs, incl. a 17k-event log with 9 compactions and a chunk-only-usage
session.
2026-08-16 00:47:00 +08:00
bowenliang123 3951da0f42 feat(ui): headline the provider-based occupancy, matching the chat ring
The overview's headline is now the provider-reported CURRENT occupancy
(contextPressure formula: newest usage sample + heuristic surface movement)
— the same figure the official chat ring shows — with the heuristic estimate
as the secondary label. On the live session: 475.1k (48%) provider vs 409.0k
(41%) heuristic; the gap is the provider tokenizer vs the 4-chars/token
heuristic (CJK-heavy content undercounts ~15%), not missing data — our
heuristic fold matches dsh's official fold to the token.
2026-08-15 08:42:08 +08:00
bowenliang123 c78345076f fix(host): shadow-price removal follows shadowedSeqs, not the declared range
The producer's compaction shadow price covers the shadowedSeqs — which can
include prune replacement nodes whose own seqs lie beyond the declared range
end. Removing by the range left those nodes in the surface and overcounted
messages by 8 x the replacement price (10,416 tokens on the live session).
Removal now follows the armed shadowedSeqs (per-category bookkeeping kept),
making our heuristic totals exactly match dsh's official contextBreakdown
fold. Also: provider-based actual occupancy in the overview now uses the
official contextPressure formula (sample + surface movement), and the
missing 'overview.actual' dict key is added.
2026-08-15 04:07:03 +08:00
bowenliang123 475c63667d docs: show the step-granularity hover tooltip in the README 2026-08-15 03:40:07 +08:00
bowenliang123 3acec4c87e feat(ui): instant hover tooltip and turn-aware dimming on the history chart
- replace the delayed native title with an instant custom .lc-chart-tip glued
  to the hovered bar's column (single line: turn/step, time, total, actual)
- turn-aware dimming: while a turn is focused (bar or strip hover), bars and
  strip blocks outside the active turn fade to 35%; the whole current turn
  and its strip block stay fully opaque
2026-08-15 03:38:15 +08:00
bowenliang123 b96eacad49 chore: bump version to 0.7.3 2026-08-15 03:25:08 +08:00
bowenliang123 ae8b75d326 ci: pin release workflow actions to their latest majors (checkout v7, setup-node v7, action-setup v6) 2026-08-15 03:22:50 +08:00
bowenliang123 fbd529939f ci: publish to npm via OIDC trusted publishing on release tags 2026-08-15 03:22:21 +08:00
bowenliang123 a162876329 chore: bump version to 0.7.2 2026-08-15 03:11:31 +08:00
bowenliang123 2e9ced209a docs: add social preview image and show it atop the README 2026-08-15 03:07:05 +08:00
bowenliang123 4171e214f7 chore: bump version to 0.7.1 2026-08-15 02:57:22 +08:00
bowenliang123 448f933929 update README.md 2026-08-15 02:55:46 +08:00
bowenliang123 233fe77056 docs: polish README header wording 2026-08-15 02:54:41 +08:00
bowenliang123 82176d4f95 docs: user-focused README with badges and fresh feature screenshots 2026-08-15 02:48:33 +08:00
bowenliang123 4f3675b51a chore: bump version to 0.7.0 2026-08-15 02:23:54 +08:00
bowenliang123 5ee3faf0d1 refactor: ponytail follow-up — drop pass-through view, dead guards, dedup react require 2026-08-15 02:22:04 +08:00
bowenliang123 4ad06a1ccc chore: move tests to tests/, add repro script, document the new structure 2026-08-15 02:19:39 +08:00
bowenliang123 6cb6a96980 refactor: layered module split with first-screen optimizations
- shared/types.ts: single wire contract (type-only) used by both halves
- host/: services (contracts), pricing (token-meter), fold (incremental fold
  + retention), snapshot (build + event turn/step attribution), index (RPC)
- client/: i18n, styles, format, categories, viewkit (shared deps),
  cache (per-session stale-while-revalidate), components/* (one module per
  component), view (composition root), index (plugin entry)
- first-screen: initial render seeds from the session cache, polling pauses
  while the tab is hidden (resume + immediate fetch on visible), and a
  transient fetch error never blanks already-visible data
- build.mjs: bundle:true for both halves (react external in the client
  bundle); the client half is now covered by tsc --noEmit for the first time
2026-08-15 02:19:28 +08:00
bowenliang123 78db333048 feat(ui): hover the free window space in the composition bar 2026-08-15 01:59:48 +08:00
bowenliang123 b9853494d7 feat(ui): context stats board above the current composition 2026-08-15 01:56:25 +08:00
bowenliang123 ee0e3b2a4f feat(ui): history detail header shows the boundary event gap as a chip 2026-08-15 01:44:21 +08:00
bowenliang123 2e2debb81e feat(ui): boundary events show the gap as a step/turn range 2026-08-15 01:35:50 +08:00
bowenliang123 f93c7b40a3 feat(host): stamp each event with the request before and after it 2026-08-15 01:35:49 +08:00
bowenliang123 888bb23078 feat(ui): show the turn/step a context event contributed to 2026-08-15 01:23:48 +08:00
bowenliang123 d8f9324693 feat(host): attribute context events to the request that follows them 2026-08-15 01:23:48 +08:00
bowenliang123 fa85b6f37b chore: bump version to 0.6.2 2026-08-15 01:17:50 +08:00
bowenliang123 9c0adb5dcc fix(ui): stop the edge-fade setState cascade that whites out the tab on granularity switch
The TrendChart layout effect has no deps and dispatched setEdges on every
render. Steady state survived via React's same-value eager bailout, but a
granularity switch genuinely flips the fades (narrow turn chart has no
overflow), and the programmatic scrollLeft assignment fires an async scroll
event whose dispatch leaves the fiber with pending lanes — the eager
bailout is skipped, every commit's effect enqueues yet another update, and
the queue grows without bound until React #185 (maximum update depth)
throws and the slot's error boundary blanks the tab.

Mirror the fades in a ref and never dispatch when the values are unchanged,
killing the cascade at the source instead of relying on bailout internals.

The real-React repro now emulates layout well enough to catch this class:
scrollWidth follows the bar count and programmatic scrolls dispatch an
async scroll event, so the old code crashes the test and the fix passes.
2026-08-15 01:15:33 +08:00
bowenliang123 44b0d3b998 remove img.png 2026-08-15 01:14:42 +08:00
bowenliang123 47c946969f docs(ui): refresh the history hint to match current interactions
The old text only mentioned clicking for details; hovering now previews
the detail (click pins), and the Step/Turn toggle changes granularity.
Both languages updated.
2026-08-15 00:48:15 +08:00
bowenliang123 2716066563 feat(ui): label turns as 'Turn {n}' with capitalized Step
Tooltips and detail headers now read 'Turn 44 · Step 5' (step mode) and
'Turn 44 · 5 steps' (turn mode) instead of the terse 'T44 · step 5';
Chinese uses the natural '第 44 轮 · 第 5 步'. The compact turn strip
blocks keep the short 'T44' label (14px columns cannot fit 'Turn').
2026-08-15 00:47:41 +08:00
bowenliang123 86b189e6e0 chore: bump version to 0.6.1 2026-08-14 23:52:04 +08:00
bowenliang123 895ebbee1a feat(ui): granularity switches re-anchor the history at the newest bars
Returning from turn to step mode kept the stale left edge (the narrow
turn chart had clamped the scroll position), so the newest bars were off
screen. The layout effect now detects a granularity change and re-runs the
first-mount anchor — both directions land on the right edge. Plain
re-renders (polls) still respect a scrolled-away position.
2026-08-14 23:49:19 +08:00
bowenliang123 7eaec6d70b update screenshot.png 2026-08-14 23:48:31 +08:00
bowenliang123 d09173ccd5 test: real-React + jsdom render smoke for the granularity toggle
Mounts the built client bundle with actual react/react-dom inside jsdom,
feeds a 460-request snapshot through the RPC, and toggles step -> turn ->
step — the flow reported to black-screen the tab. Renders cleanly, so the
black screen is not in the render path (layout-effect suspicion remains,
needs the real console error to pin down).
2026-08-14 23:44:49 +08:00
bowenliang123 4fee890b34 feat(ui): hovering a composition legend chip lights the segment and shows its tooltip
The tooltip is now derived from the shared hover key instead of internal
pointer state: hovering either a segment or its legend chip brightens the
same segment, highlights the chip, and shows the same tooltip centered on
the segment (percentage positioning, no measuring). Moving the pointer
within a segment no longer repositions the tooltip — the centered
placement is consistent for both directions.
2026-08-14 23:34:57 +08:00
bowenliang123 50f4c9fced fix(ui): step/turn toggle text is legible in dark mode
The active button used white text on the brand color, but dark themes
resolve brand-primary to a near-white shade — white-on-white. Now uses
the harness's own primary-button pairing (button-primary-fill background,
label-primary-foreground text), which flips to dark text on the light
brand in dark mode.
2026-08-14 23:32:31 +08:00
bowenliang123 947e2b2bb9 chore: bump version to 0.6.0 2026-08-14 23:28:56 +08:00
bowenliang123 f8d66d08b2 fix(host): price assistant replies and tool results by their content
The TS port passed the event ENVELOPE (data) instead of the model-visible
message (data.message) for assistant/message and tool/result — every such
node was priced at a flat 4 tokens (role framing only), so tool-heavy and
assistant-rich sessions were massively undercounted in the Current
composition. This is why the overview (20.4k) disagreed with the real
usage. Mirrors dsh's deriveEventMessage exactly now; empty-content
assistant messages price 0; tools total uses dsh's whole-array price; the
overview additionally shows the last provider-reported actual prompt
alongside the estimate.
2026-08-14 23:27:04 +08:00
bowenliang123 72667cd5d3 fix(host): retain history by whole turns, not step count
The fold capped the timeline at a step count, so long sessions lost
older turns entirely in turn granularity. When the step bound is
exceeded, the timeline is now trimmed to the newest 300 WHOLE turn runs
(1500-step backstop for pathological many-step turns) — a turn is never
cut in half, and turn mode always shows the full recent turn range.
The harness session log itself has no event cap, so this was the only
limiter.
2026-08-14 23:06:16 +08:00
bowenliang123 a49a1c6fba chore: husky pre-commit hook runs tsc --noEmit
Every commit is now type-guarded: the hook runs `pnpm run typecheck`
(strict, noEmit), so type errors fail the commit. Husky auto-installs the
hooks on install via the prepare script.
2026-08-14 23:03:58 +08:00