Skip to content

Releases

v1.8.1 — fresh picker data, abortable requests, accessible timeline

Choose a tag to compare

@2310770803 2310770803 released this 05 Nov 00:35

A focused cleanup pass addressed three real edge cases without changing the feature contract.

Fresh data

The picker no longer caches an entry list indefinitely. Opening it always asks the Host again, so messages added while the dialog was closed and new compaction markers cannot stay hidden behind stale data.

Request stability

Snapshot deadlines now use AbortController. When the 45-second deadline fires, the in-flight fetch is aborted instead of continuing to consume Host and browser resources after the UI has already reported a timeout. Older browsers without AbortController keep the previous deadline behavior.

Timeline accessibility

The timeline rail is now a labelled navigation region instead of aria-hidden; its jump markers can receive keyboard focus and expose their turn and date labels to screen readers.

Cleanup

Removed the unused INCOMPLETE_MARKER constant and corrected stale route/plugin comments and README claims after the raw-log-first history fix.

The installed copy passes all four suites: 46 + 10 + 16 + 16 checks.

v1.8.0 — Host buttons, concise copy, lighter chrome

Choose a tag to compare

@2310770803 2310770803 released this 01 Nov 02:35

The picker had a visual language of its own: hand-drawn buttons, a segmented control and pill chips competing with the surrounding Host UI. Every real button is now the Host's own Button primitive.

Buttons

  • Button is a forwardRef object, not a function — that is why this plugin used plain <button> elements before — but React accepts it, so the earlier problem was only ever a type guard. A uiButton factory renders primitives.Button when the release exports one and an equivalent fallback otherwise, so call sites read the same either way.
  • Footer uses the Host's variants: ghost Cancel, outline Export .md, primary Copy. Toolbar actions, the turn toggle, the role chips and the oldest/newest pair follow the same pattern, with primary marking the active state.
  • The header trigger and the dialog close are Host ghost icon buttons, so they match the rest of the title bar exactly instead of approximating it.
  • Only the 7px rail dots stay hand-drawn — they are marks, not buttons.
  • The stylesheet drops the classes that existed to imitate those buttons (52 class rules → 45) and keeps action* solely for the no-primitive fallback.

Concise copy

  • 选择消息 / Select messages, one-line descriptions, Filter….
  • Meter reads 2/8 · 496 chars instead of a sentence; turn sizes are a bare number; Clear turn → Clear; Copy selected → Copy; Export selected to file → Export .md; the menu entry is Copy directory.
  • Hint: Shift to range · Space to toggle · Ctrl+A for all.
  • Transitions on rows, chips, buttons and rail dots; hovering grows a rail dot; scrollbar-gutter: stable keeps the list from shifting.

Tests

All suite selectors moved off implementation classes onto semantics — chips by aria-pressed and label, order buttons by aria-pressed, the turn toggle by its title — so a future restyle cannot silently break them. 46 + 10 + 16 + 16 checks pass against the installed copy.

v1.7.0 — dated timeline headers, and oldest/newest order

Choose a tag to compare

@2310770803 2310770803 released this 01 Nov 02:18

Two requests, both about finding a stretch of conversation.

Dates

Every turn header now carries the calendar date as well as the clock range — Oct 23 13:20–13:26 — because readers look for "the one from the 23rd", not "the one at 13:20". A turn that crosses midnight renders both dates (Oct 23 23:50 – Oct 24 00:12). Row times stay HH:MM, since the header above them already names the day.

Order

Oldest / Newest in the toolbar flips the whole timeline: the groups, the rows inside each group, and the rail — so newest-first starts at the most recent exchange and still reads naturally downwards. The scroll spy keeps working because it walks the rendered order.

Ordering is presentation only. Selection keys off each entry's index, so a tick in either direction names the same message — asserted in the tests.

Fixed while wiring this

orderedGroups dereferenced picker when the dialog was closed, which threw on the next render after closing. It is now guarded, and the client suite exercises open/close/open.

Tests

The client suite grows to 46 checks, adding the dated header, the range keeping its clock, both order buttons, newest-first reversing groups and rows, the rail following the order, and a reversed-order copy sending index [2] for the same message. All four suites (46 + 10 + 16 + 16) pass against the installed copy.

v1.6.1 — the picker now shows the whole history again

Choose a tag to compare

@2310770803 2310770803 released this 01 Nov 02:09

Long-ago messages were missing from the picker and from the Markdown copy. That was my bug.

The picker and the copy were reading sessionQuery.readSurface — what the model currently sees — in preference to readSession, the complete raw log. That is right for a model and wrong for a transcript, because compaction replaces everything before it: a compaction/summary (or the compact-checkpoint user message carrying it) declares surfaceOp: {op:'replace', startSeq, endSeq} over the whole earlier range.

Measured on the session that showed the problem:

Entries Turns covered Compaction markers
readSurface (before) 247 14–22 0
readSession (after) 398 1–22 4

The read order is reversed: the raw log is the source, readSurface only the fallback when the log cannot be read or replayed. Extraction is unchanged, so compaction is still marked in place and the injected checkpoint messages are still skipped rather than duplicated.

Tests

A new source suite (16 checks) asserts the log is used, that the surface is never consulted when the log reads, that a failed log read still falls back, that pre-compaction text and the compaction marker survive, and — on a real session — that the entries match a full local extraction one for one. A sweep over all 104 sessions compares the route's list against a local extraction: 6,731 rows, 27 compaction markers, zero mismatches.

v1.6.0 — export Markdown to a file, and stop clipping the menu

Choose a tag to compare

@2310770803 2310770803 released this 31 Oct 06:00

Two problems, both about length.

Export to a file

A long conversation could not be copied in full: the clipboard action has a 220,000-character cap because a single clipboard write of a few hundred kilobytes is unreliable — and the cap is exactly where content was going missing.

Measured on a real 4.3 MB session log:

Characters Complete?
Clipboard copy 212,330 no — trimmed at the cap
File export 778,435 yes
  • Export Markdown file in the menu saves the whole conversation as .md.
  • The picker gains Export selected to file next to Copy selected.
  • File requests set unclamped, so the Host skips the clipboard cap and applies an 8,000,000-character bound instead — still finite, so a corrupt log cannot exhaust Host memory. Clipboard requests keep their cap and keep saying when they trim.
  • The filename is scrubbed for Windows and macOS: control characters removed, \/:*?"<>| replaced, trailing dots and spaces trimmed, 80-character limit.

Menu labels

The labels were too long and the last one was clipped: Copy as Markdown → Copy Markdown, Copy selected messages… → Select messages…, and the popover minimum width goes from 188px to 216px so the new export entry fits.

Tests

A new export suite (16 checks) covers the unclamped request, the sanitised filename, the blob contents and type, the success notice, and that the picker export sends the ticked indices.

v1.5.0 — a timeline for the message picker

Choose a tag to compare

@2310770803 2310770803 released this 31 Oct 05:52

A picker that lists 374 messages in one flat column gives no sense of where you are in the conversation. It is now grouped by turn, with a rail to navigate it.

Timeline

  • The list is grouped by turn. Each group has a sticky header with the turn number, its time range and its size, so scrolling never loses which exchange you are looking at.
  • A rail down the left marks every turn: click a marker to jump to that turn, and the active marker follows your scroll position.
  • Every row shows the time its message was sent; the full stamp is in the tooltip.
  • Whole turn on a header ticks or clears every message in that exchange — usually the unit you actually want.

Under it

  • Host items now carry the event's time and the turn they belong to. turn/start and assistant/message declare data.turn; user/message does not, so extraction tracks the current turn and items inherit it. Verified over 102 real sessions: no item ends up without a time or a turn, and turn numbers never go backwards.
  • Grouping keys off the same index the selection already uses, so grouping cannot shift which message a tick refers to.
  • The copied Markdown is unchanged by this release.

Tests

The client suite grows to 35 checks, the focus harness to 10 (adding the rail jump and the scroll spy in both directions), and the stylesheet check runs in CI at 49 classes.

v1.4.1 — a properly proportioned picker and a softer scrollbar

Choose a tag to compare

@2310770803 2310770803 released this 31 Oct 05:44

The dialog rendered narrow and very tall on a wide screen, with the scrollbar on the frame instead of on the message list.

The cause: it borrowed the Host's Modal, which applies its own width to its frame — so the plugin's own width rules never applied, and the frame, not the list, became the scroll container.

Layout

  • The picker now owns its dialog frame on every Host version: 920px wide (94vw below 1040px), height bounded by the viewport.
  • The message list is the only scroll container, so the header, chips and footer stay put while rows scroll.
  • What Modal would have supplied is covered explicitly: mask dismissal, Escape, an initial focus target (the filter box), ArrowDown to leave the filter for the list, and a Tab loop that wraps inside the dialog.

Scrollbar

A 12px track with a pill thumb inset by 4px, a hover state, plus scrollbar-color and scrollbar-gutter so the same treatment applies outside Chromium and the list does not shift when the bar appears.

Tooling

scripts/check-stylesheet.mjs moves into the repository and into CI. It exists because this failure mode is invisible to node --check — the rules are a JavaScript string — and because the previous release shipped a rule spliced into another rule, which silently killed every rule inside the swallowed block. It asserts balanced braces, no block inside a declaration block (at-rules exempt), no declaration running into the next selector, and that every class the client assigns has a rule.

v1.4.0 — make the picker fast to drive

Choose a tag to compare

@2310770803 2310770803 released this 31 Oct 05:36

Selecting a stretch of a 300-message session meant scrolling and clicking row by row. The picker now has the gestures a chat app has.

Interaction

  • Role chips (All / You / Assistant / Compaction) narrow the list to one side; only the roles actually present are offered. Chips and the filter both scope Select all, so "every question I asked about the parser" is one chip plus one word.
  • Shift-click selects a whole range, and a range never escapes the current filter or chip.
  • Keyboard: Space toggles the focused row, ↑/↓/Home/End move between rows, Ctrl/⌘+A ticks everything visible, Escape closes.
  • The meter reports size as well as count — 4 of 7 · 1,832 characters — so the cost of a paste is visible before making it.
  • Row checkboxes are drawn by the plugin (a themed box with a tick), which is what makes the range gesture and the checked-row tint possible; rows are role="checkbox" with their own key handling.
  • The entry list is cached per session, so reopening the dialog is instant.

Styling fix

The chip and hint rules had been spliced into the middle of the .dshSessionCopyPickerError declaration block, so the CSS parser swallowed every rule inside it: chips rendered as unstyled native buttons and the footer hint had no styling. node --check cannot catch that — the stylesheet is only a string — so this release adds a stylesheet integrity check that asserts balanced braces, no nested block, no declaration running into the next selector, and that every class the client assigns has a rule.

v1.3.1 — explain an empty picker instead of blaming the session

Choose a tag to compare

@2310770803 2310770803 released this 31 Oct 01:12

A fix for a confusing state, found while trying the picker on a running app.

The browser half of a plugin hot-swaps when the page reloads, but the Host half only changes when DeepSeek Harness restarts. In that window the new picker asked for the message list, the old Host ignored mode and answered with a Markdown document, and the dialog reported "this session has no messages" — a dead end that blamed the session for what was really an unloaded Host.

The client now requires an entries array in the response and, when it is missing, says plainly that the Host has not loaded the build that supports message selection and to quit and reopen DeepSeek Harness.

No other behaviour changed. Verified with the client suite plus a variant that answers exactly like a 1.3.0 Host.

v1.3.0 — pick which messages to copy

Choose a tag to compare

@2310770803 2310770803 released this 31 Oct 00:57

The Markdown action could only copy a whole session. A long session usually has one stretch worth sharing — the debugging thread, the decision, the answer — and there was no way to take just that.

A new menu item, Copy selected messages…, opens a picker that works like multi-select in QQ/WeChat: every message with a checkbox, a filter box, a live counter, and a copy button that copies exactly what is ticked.

The picker

  • Filter narrows hundreds of rows; Select all applies to what the filter shows, so "everything about the parser" is two clicks.
  • The copied document opens with > Excerpt: 12 of 340 messages selected, so a pasted fragment is never mistaken for a truncated export.
  • Compaction checkpoints appear as their own rows, so an intentional summary can be included or left out on purpose.
  • Nothing is written to the clipboard until Copy selected is pressed; Escape and the mask close the dialog.

Correctness

  • A selection is trusted only as indices: out-of-range, repeated or non-integer values are dropped rather than allowed to shift the result onto a different message, and the Host reports how many were discarded.
  • The dialog renders through the Host's Modal primitive when the release exports one, and in this plugin's own overlay otherwise, from identical children so the two paths cannot drift.
  • The request body cap moved from 4 KiB to 128 KiB, since a session can have several hundred selectable messages.

Tooling

scripts/install.mjs --check used to fail on any @deepseek-ai/dsh* package in the profile; it tripped over dsh-home-paths, a legitimate dependency of an unrelated installed plugin. It now fails only on a shadowing copy of this plugin's own peers and reports the rest as information.