Skip to content
dsh-market Browse plugins GitHub 中文

enterhalf/dsh-session-colorful-unread-pin-jobs

Colors each DSH sidebar session title by state: unread (blue while the model is running, green once it has finished) and pinned (yellow, read from DSH's own pinned set), composed into one left-to-right gradient. Pinning, archiving and ordering stay with DSH itself — the plugin reimplements no pinning and rewrites no official bundle — and a settings section lists every unread session.

Stars ★ 0 Category UI Enhancements Listed 2026-09-19 npm dsh-session-colorful-unread-pin-jobs

Install

Inside DeepSeek Harness, with dsh-market

dsh plugin --profile web add dshmarket

Or from the command line

dsh plugin --profile web add dsh-session-colorful-unread-pin-jobs

Installing runs third-party code with your own permissions — it can read your files, use your credentials and reach the network. Review the source first, and pin a commit (github:owner/repo#sha) when you can.

Screenshots

README

中文 | English

See at a glance which sessions in the DSH sidebar still hold output you have not looked at, and which have finished. Session titles are painted by state: unread (blue while the model is running, green once it has finished) and pinned (yellow, read from DSH's own pinned set). When both apply they are composed into one gradient — pin yellow on the left, unread blue/green on the right. Colors are derived from the current theme's brand color and adapt to light, dark, and monochrome themes automatically.

Pinning, archiving, and ordering belong to DSH itself (built in since 0.1.7-alpha.2): the menu entry, the pin icon, the pinned set, and pinned-first ordering are all official, and this plugin deliberately reimplements none of them — it only reads the official pinned set to tint titles. It also never rewrites official bundles: the menu uses an official slot and painting uses the session row DOM, so uninstalling leaves nothing behind.

What it looks like

Session titles in the sidebar painted by state: pinned yellow, unread blue/green, pinned+unread composed into a yellow→blue gradient

The session row "…" menu: official pin/rename/fork/archive plus this plugin's "Mark as unread"

Settings → "Session unread": every unread session, mark-read per row, clear-on-send toggle

These are illustrative mockups generated by assets/mockups/generate.mjs using the real DSH theme tokens — not screenshots, and they contain no user session data.

Highlights

State Color Trigger
Unread · running brand-blue gradient The model produced content in any session (including the one you have open) and that session is still running
Unread · finished green gradient The condition above stops holding (the session went idle) — the job is done
Pinned yellow gradient Official pin (session row "…" → "Pin session"); the plugin only reads the official pinned set
Both at once yellow → blue/green Composed in semantic order: pin leftmost, unread rightmost
  • Nothing to watch: a session that finishes turns blue → green on its own, so one glance at the sidebar tells you which sessions came back with results.
  • Nothing lost: the unread set is persisted server-side and survives page reloads and process restarts.
  • Purely additive: official bundles are never rewritten and official behavior is never replaced — the plugin only tags rows and paints titles, and uninstalls cleanly.

Features

  1. Automatic unread — the moment the model produces content in any session (including the one you have open) through a whitelisted event (assistant/chunk content blocks, assistant/message, tool/result), that session gets the unread gradient. The color distinguishes work state: brand blue while running, green once the session went idle (the running flag is read from the session-list store and re-evaluated on every store update).
  2. Clearing — opening, staying, and sending never clear the mark. Switching away from the current session (A → B) clears A; clicking the current session row a second time also clears it (the native "select = acknowledge" semantics). Both paths remove the mark locally at once and persist it to the host.
  3. Title painting (one composer) — all visuals collapse into a single path: row state → anchor colors → paint. Semantic order, left to right: pinned = yellow (leftmost) → unread = blue (running) / green (finished, rightmost). One state paints a whole two-stop gradient of that family (unread derives a same-family pair from the theme brand color, degrades on monochrome themes, and every end color passes a background-contrast guard); both states expand the segmented template (solid zones plus a transition). Gradients are written as inline styles on the title element, which React re-renders never overwrite, so no repaint is ever needed. Stops are adapted to the real text width: short titles compress the stops to px, long titles use container-width percentages.
  4. Clear on send (optional, off by default) — when enabled in settings, sending a message inside a marked session also drops that mark.
  5. Manual toggle — the session row "…" menu gains one "Mark as unread / Mark as read" entry, registered through the official sidebar.workspaces.session.menu.item slot and placed after the shipped entries.
  6. Official pin, read-only — the pinned set comes from the official workspaces.list snapshot (pinnedSessionIds, archived rows excluded per official semantics). The plugin never writes pins and never reorders for them; official pinning behaves exactly as shipped.
  7. Settings section — Settings → "Session unread": every unread session (title, running marker, session id), a manual Refresh, per-row Mark read, and the clear-on-send toggle.
  8. Zero bundle rewriting — the menu and settings sections use official slots, and painting observes the session row DOM (data-row-key="session:<id>" plus the title class suffix _title). There is no anchor-string replacement any more, and transformed output is never written back onto official client.js files.

Install

dsh plugin --profile web add dsh-session-colorful-unread-pin-jobs@latest

From GitHub (equivalent):

dsh plugin --profile web add github:enterhalf/dsh-session-colorful-unread-pin-jobs

Uninstall:

dsh plugin --profile web remove dsh-session-colorful-unread-pin-jobs

Requires DSH ≥ 0.1.7-alpha.2 (the new sidebar: official pinning, official slots, and row data-row-key). On older DSH builds (npm latest is still 0.1.5-rc.3) stay on this plugin's 1.0.0.

Uninstall is clean: slot registrations, routes, event subscriptions, timers, and observers are all removed with the fiber. The unread set and settings stay in ~/.dsh/storages/dsh-session-colorful-unread-pin-jobs/ and are picked up again on reinstall.

How it works

  • Unread data plane (host-persisted) — the host subscribes to session/event and marks the non-subagent session unread when a whitelisted event lands (idempotent; no disk write when nothing changed). Storage is ~/.dsh/storages/dsh-session-colorful-unread-pin-jobs/unread.json (atomic write, at most 2000 entries with FIFO eviction, plus the clearOnSend setting). The client reconciles against GET /session-colorful-unread-pin-jobs/list every 2 seconds; the host is authoritative.
  • HTTP plane (registered on the webServer route table under /session-colorful-unread-pin-jobs):
    • GET /list → { ok, ids }
    • POST /toggle { id, unread } → { ok, ids }
    • GET/POST /settings → { ok, clearOnSend }
    • (the v1 /top, /active, and /timestamps endpoints are gone along with the self-owned pin and sort planes)
  • UI plane (official extension points):
    • menu entry: ctx.slots.inject('sidebar.workspaces.session.menu.item');
    • settings page: ctx.slots.inject('settings.section');
    • painting: a MutationObserver plus the 2-second poll drive syncRowMarks(), which scans [data-row-key^="session:"], composes a gradient from the host unread set and the official pinned set (ctx.get('workspaces').list), and writes it as inline style on that row's title element;
    • copy: registered in the official locale namespace session-colorful-unread-pin-jobs (follows the UI language; falls back to Chinese when no locale service is present).
  • One-time residue cleanup — 1.x wrote transformed output back onto official client.js files under the new architecture. On start, 2.0.0 checks whether a bundle still carries v1 injection markers and, if so, restores the original bytes from the bundle-*.client.js.orig sidecar in storages and realigns the clientModules graph. On a clean install this is a no-op.

Upgrading from 1.x to 2.0.0

Breaking changes (hence the major version):

  • The plugin's own pinning is gone (menu entry, topIds persistence, /top endpoint, pin ordering partition). Use the official "Pin session" instead — it already persists pins and orders pinned sessions first.
  • The "Model updated" sort mode, the /timestamps endpoint, and the Deep diving "since last agent output" second column are gone.
  • The "live background shell = purple" marker is gone (the new session-list snapshot no longer carries a jobs mirror, and the capability overlaps the official background-job UI).
  • Official bundles are no longer rewritten, so anchor self-checks are unnecessary (check-anchors / check-migration were replaced by check-env / check-host).

Upgrade steps:

  1. After upgrading the plugin, restart DSH (the host half loads with the process; the client half is hot-updated by client-hmr's 500 ms poll and applies on a page refresh).
  2. The unread set and the clear-on-send setting carry over; topIds in unread.json is ignored (never read or written again) — re-pin through the official menu.
  3. Any on-disk residue is restored automatically by the host half; the bundle-*.client.js.orig sidecars stay as a v1 rollback path and may be deleted by hand.

Limitations

  • Requires DSH ≥ 0.1.7-alpha.2: older builds have no official slots and no data-row-key, so the UI plane does not activate (the host data plane still works). npm run check-env verifies the installed tree against the contract offline.
  • Painting depends on two stable row-DOM anchors: data-row-key="session:<id>" and the title class suffix _title. If DSH changes either, the plugin needs re-adapting — but unlike the old bundle-anchor replacement, a mismatch only means "no color", never a broken page or menu.
  • Session rows inside a collapsed workspace group are not rendered, so those rows color when the group is expanded (the observer paints them as soon as they appear).
  • "The current session" is the holder of retainedBy.mainView in the list store; each browser tab decides and clears independently.
  • Theme adaptation relies on the theme system writing the theme onto the body attribute and exposing the brand color through alias variables; a third-party theme that changes colors another way will not be followed.
  • Automatic unread is decided at whitelisted model-event granularity: content-free anomalies such as llm/retry do not count.

Development

npm run build          # check and emit lib/index.js
npm run build:client   # check and emit lib/client.js
npm run check-host     # host-half unit tests: storage/migration, HTTP plane, v1 residue cleanup
npm run check-env      # offline check that the installed DSH tree exposes the official extension points
npm run check          # both of the above
dsh plugin --profile web add /path/to/dsh-session-colorful-unread-pin-jobs

License

MIT

Content from the project README on GitHub ↗

Comments

Comments live in GitHub Discussions. Sign in with GitHub to post or react.