Commit Graph
9 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 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 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 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 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