{"_id":"@dennisrongo/dsh-plan-board","_rev":"4-1cda239fa9945e634f15438ec43feb26","name":"@dennisrongo/dsh-plan-board","dist-tags":{"latest":"0.3.1"},"versions":{"0.1.0":{"name":"@dennisrongo/dsh-plan-board","version":"0.1.0","author":{"name":"Dennis Rongo"},"license":"MIT","_id":"@dennisrongo/dsh-plan-board@0.1.0","maintainers":[{"name":"dennisrongo","email":"dennis@menacestudio.com"}],"homepage":"https://github.com/dennisrongo/dsh-plugins/tree/main/plugins/dsh-plan-board#readme","bugs":{"url":"https://github.com/dennisrongo/dsh-plugins/issues"},"dsh":{"bundle":{"patch":"./cordis.patch.yml"},"client":{"inject":["@deepseek-ai/dsh-client-runtime"],"platform":"web","immediately":true}},"dist":{"shasum":"862fbbddb714ea59e270d9145264535eb9a5687f","tarball":"https://registry.npmjs.org/@dennisrongo/dsh-plan-board/-/dsh-plan-board-0.1.0.tgz","fileCount":6,"integrity":"sha512-7vGbVjhn3oMuyV8pugWIq2j9VpBz8KrxAU27Uu72oQjsNjTxp23ZbE0QncaMRrmI7pTkOnPeb5hcr9zDrsdf6g==","signatures":[{"sig":"MEUCIQC2DHN82GIR2Og6+h1vypX7ekGqQ5hdnGx52v3KbctOXAIgBc3An4hD69f/R5P4RCaUiZ9R56pQs3rsnM3W/u6NXhY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@dennisrongo%2fdsh-plan-board@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":375671},"main":"lib/index.js","type":"module","exports":{".":{"default":"./lib/index.js"},"./client":{"default":"./lib/client.js"},"./typert":{"default":"./lib/typert.host.js"},"./package.json":"./package.json"},"gitHead":"f3f56e3e0d834f8bf484ed327bda5e15afe71e5a","scripts":{"test":"node build/build.mjs && node test/smoke.mjs","build":"node build/build.mjs","typecheck":"tsc --noEmit"},"_npmUser":{"name":"dennisrongo","email":"dennis@menacestudio.com"},"repository":{"url":"git+https://github.com/dennisrongo/dsh-plugins.git","type":"git","directory":"plugins/dsh-plan-board"},"_npmVersion":"10.9.8","description":"Durable plans for DeepSeek Harness (dsh) — captures every exit_plan_mode plan to <workspace>/.dsh/plans as markdown, opens a review window when one is presented, and keeps the history","directories":{},"_nodeVersion":"22.23.2","dependencies":{"zod":"^4.4.3"},"_hasShrinkwrap":false,"devDependencies":{"react":"^18.3.0","esbuild":"^0.24.0","react-dom":"^18.3.0","typescript":"^5.6.0","@types/node":"^22.20.1","@types/react":"^18.3.0","@types/react-dom":"^18.3.0"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.1","@deepseek-ai/dsh-tools":"^0.1.1-rc.2","@deepseek-ai/dsh-workspace":"^0.1.1-rc.2","@deepseek-ai/dsh-typert-protocol":"^0.1.1-rc.2"},"_npmOperationalInternal":{"tmp":"tmp/dsh-plan-board_0.1.0_1788036374721_0.5060694934607124","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@dennisrongo/dsh-plan-board","version":"0.2.0","author":{"name":"Dennis Rongo"},"license":"MIT","_id":"@dennisrongo/dsh-plan-board@0.2.0","maintainers":[{"name":"dennisrongo","email":"dennis@menacestudio.com"}],"homepage":"https://github.com/dennisrongo/dsh-plugins/tree/main/plugins/dsh-plan-board#readme","bugs":{"url":"https://github.com/dennisrongo/dsh-plugins/issues"},"dsh":{"bundle":{"patch":"./cordis.patch.yml"},"client":{"inject":["@deepseek-ai/dsh-client-runtime"],"platform":"web","immediately":true}},"dist":{"shasum":"1ebf549c19420c023c1b8275fcea8e14ad6cf37c","tarball":"https://registry.npmjs.org/@dennisrongo/dsh-plan-board/-/dsh-plan-board-0.2.0.tgz","fileCount":6,"integrity":"sha512-FghtRiuS1RR5wkLFiQ7Xf0xfrqe+aFfOTCV1ZEnJUX5ved1nB4JnqpSRE/gTZkH91CwdIjfqLnMCU1/YpCvnFg==","signatures":[{"sig":"MEQCICgC3hnJ0bA5EV8eZtisMczb9yfp80aAXLn2jH71M+5oAiBLiPFXeJ5CPxb/lpPz8LYb58wzNW3nRwSHn/DUXCj45g==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@dennisrongo%2fdsh-plan-board@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":393376},"main":"lib/index.js","type":"module","exports":{".":{"default":"./lib/index.js"},"./client":{"default":"./lib/client.js"},"./typert":{"default":"./lib/typert.host.js"},"./package.json":"./package.json"},"gitHead":"5d44dcf43c62c7929574d5cd52a014f8d601ba45","scripts":{"test":"node build/build.mjs && node test/smoke.mjs","build":"node build/build.mjs","typecheck":"tsc --noEmit"},"_npmUser":{"name":"dennisrongo","email":"dennis@menacestudio.com"},"repository":{"url":"git+https://github.com/dennisrongo/dsh-plugins.git","type":"git","directory":"plugins/dsh-plan-board"},"_npmVersion":"10.9.8","description":"Durable plans for DeepSeek Harness (dsh) — captures every exit_plan_mode plan to <workspace>/.dsh/plans as markdown, opens a review window when one is presented, and keeps the history","directories":{},"_nodeVersion":"22.23.2","dependencies":{"zod":"^4.4.3"},"_hasShrinkwrap":false,"devDependencies":{"react":"^18.3.0","esbuild":"^0.24.0","react-dom":"^18.3.0","typescript":"^5.6.0","@types/node":"^22.20.1","@types/react":"^18.3.0","@types/react-dom":"^18.3.0"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.1","@deepseek-ai/dsh-tools":"^0.1.1-rc.2","@deepseek-ai/dsh-workspace":"^0.1.1-rc.2","@deepseek-ai/dsh-typert-protocol":"^0.1.1-rc.2"},"_npmOperationalInternal":{"tmp":"tmp/dsh-plan-board_0.2.0_1788066795372_0.9685250005362644","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@dennisrongo/dsh-plan-board","version":"0.3.0","author":{"name":"Dennis Rongo"},"license":"MIT","_id":"@dennisrongo/dsh-plan-board@0.3.0","maintainers":[{"name":"dennisrongo","email":"dennis@menacestudio.com"}],"homepage":"https://github.com/dennisrongo/dsh-plugins/tree/main/plugins/dsh-plan-board#readme","bugs":{"url":"https://github.com/dennisrongo/dsh-plugins/issues"},"dsh":{"bundle":{"patch":"./cordis.patch.yml"},"client":{"inject":["@deepseek-ai/dsh-client-runtime"],"platform":"web","immediately":true}},"dist":{"shasum":"726a274acee6b422631e355785d54b7ac9f8c118","tarball":"https://registry.npmjs.org/@dennisrongo/dsh-plan-board/-/dsh-plan-board-0.3.0.tgz","fileCount":6,"integrity":"sha512-8hOqZ/G6a1GDYEkH4TVJyzKalaJbgUWtymFc4Oj3opiaLFVkr5xV416tIpCU5h+nuUJBL0c05GmaXscpxfYBxw==","signatures":[{"sig":"MEQCIGL28UZpRj71WnAaAwRl4oYi+Lyuzx6z0YG4nXWaHQXaAiA8Omrv9Vp59Rkz17/AxMzjxsFWxPg1u8UYUaOcWentZw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@dennisrongo%2fdsh-plan-board@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":410439},"main":"lib/index.js","type":"module","exports":{".":{"default":"./lib/index.js"},"./client":{"default":"./lib/client.js"},"./typert":{"default":"./lib/typert.host.js"},"./package.json":"./package.json"},"gitHead":"b037914e62a2b12d5c76d3387b218f96319d7a54","scripts":{"test":"node build/build.mjs && node test/smoke.mjs","build":"node build/build.mjs","typecheck":"tsc --noEmit"},"_npmUser":{"name":"dennisrongo","email":"dennis@menacestudio.com"},"repository":{"url":"git+https://github.com/dennisrongo/dsh-plugins.git","type":"git","directory":"plugins/dsh-plan-board"},"_npmVersion":"10.9.8","description":"Durable plans for DeepSeek Harness (dsh) — captures every exit_plan_mode plan to <workspace>/.dsh/plans as markdown, opens a review window when one is presented, and keeps the history","directories":{},"_nodeVersion":"22.23.2","dependencies":{"zod":"^4.4.3"},"_hasShrinkwrap":false,"devDependencies":{"react":"^18.3.0","esbuild":"^0.24.0","react-dom":"^18.3.0","typescript":"^5.6.0","@types/node":"^22.20.1","@types/react":"^18.3.0","@types/react-dom":"^18.3.0"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.1","@deepseek-ai/dsh-tools":"^0.1.1-rc.2","@deepseek-ai/dsh-workspace":"^0.1.1-rc.2","@deepseek-ai/dsh-typert-protocol":"^0.1.1-rc.2"},"_npmOperationalInternal":{"tmp":"tmp/dsh-plan-board_0.3.0_1788288072007_0.8931188422538123","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"_id":"@dennisrongo/dsh-plan-board@0.3.1","dsh":{"bundle":{"patch":"./cordis.patch.yml"},"client":{"inject":["@deepseek-ai/dsh-client-runtime"],"platform":"web","immediately":true}},"bugs":{"url":"https://github.com/dennisrongo/dsh-plugins/issues"},"dist":{"shasum":"78c24624752f9b86b4b49a082eb34b6291581426","tarball":"https://registry.npmjs.org/@dennisrongo/dsh-plan-board/-/dsh-plan-board-0.3.1.tgz","fileCount":6,"integrity":"sha512-MBwljVqXIQiX+iuN4yBJkVehiOxj6Poh84VuMaKp5iEi1P4riNT92MYrgmRWorIqCnkNZBYwOnB4AcolCut2ww==","signatures":[{"sig":"MEYCIQCHAcDZVpEeUg6z7VbOmZA2cLC8VE3nplqMvWiGmc1JCQIhANPDyYiVZx5lZEwfFIlFwk3o1ZFLeQ57yU1lo0Bc+QF8","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIDHlJxu5m89wAkv0mB76fzh3ZuhFOti0tJmD6ijX3mcWAiEAkBmHFQTb3cUAZEf25PsFtDmibKHT8NWKS5zX7GjTPdg="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@dennisrongo%2fdsh-plan-board@0.3.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":411049},"main":"lib/index.js","name":"@dennisrongo/dsh-plan-board","type":"module","author":{"url":"https://www.youtube.com/@codingmenace","name":"Dennis Rongo"},"exports":{".":{"default":"./lib/index.js"},"./client":{"default":"./lib/client.js"},"./typert":{"default":"./lib/typert.host.js"},"./package.json":"./package.json"},"gitHead":"e3786cf6b1cf7a267fb5d1b97186cbc3aa4124d3","license":"MIT","scripts":{"test":"node build/build.mjs && node test/smoke.mjs","build":"node build/build.mjs","typecheck":"tsc --noEmit"},"version":"0.3.1","_npmUser":{"name":"dennisrongo","email":"dennis@menacestudio.com"},"homepage":"https://github.com/dennisrongo/dsh-plugins/tree/main/plugins/dsh-plan-board#readme","keywords":["ai-coding-agent","claude-code-alternative","deepseek-harness","dsh","dsh-plugin"],"repository":{"url":"git+https://github.com/dennisrongo/dsh-plugins.git","type":"git","directory":"plugins/dsh-plan-board"},"_npmVersion":"10.9.8","description":"Durable plans for DeepSeek Harness (dsh) — captures every exit_plan_mode plan to <workspace>/.dsh/plans as markdown, opens a review window when one is presented, and keeps the history","directories":{},"maintainers":[{"name":"dennisrongo","email":"dennis@menacestudio.com"}],"_nodeVersion":"22.23.2","dependencies":{"zod":"^4.4.3"},"_hasShrinkwrap":false,"devDependencies":{"react":"^18.3.0","esbuild":"^0.24.0","react-dom":"^18.3.0","typescript":"^5.6.0","@types/node":"^22.20.1","@types/react":"^18.3.0","@types/react-dom":"^18.3.0"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.1","@deepseek-ai/dsh-tools":"^0.1.1-rc.2","@deepseek-ai/dsh-workspace":"^0.1.1-rc.2","@deepseek-ai/dsh-typert-protocol":"^0.1.1-rc.2"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/dsh-plan-board_0.3.1_1789592658918_0.5384778269976547"}}},"time":{"created":"2026-08-29T20:46:14.457Z","modified":"2026-09-16T21:04:19.354Z","0.1.0":"2026-08-29T20:46:14.883Z","0.2.0":"2026-08-30T05:13:15.501Z","0.3.0":"2026-09-01T18:41:12.200Z","0.3.1":"2026-09-16T21:04:19.007Z"},"bugs":{"url":"https://github.com/dennisrongo/dsh-plugins/issues"},"author":{"url":"https://www.youtube.com/@codingmenace","name":"Dennis Rongo"},"license":"MIT","homepage":"https://github.com/dennisrongo/dsh-plugins/tree/main/plugins/dsh-plan-board#readme","repository":{"url":"git+https://github.com/dennisrongo/dsh-plugins.git","type":"git","directory":"plugins/dsh-plan-board"},"description":"Durable plans for DeepSeek Harness (dsh) — captures every exit_plan_mode plan to <workspace>/.dsh/plans as markdown, opens a review window when one is presented, and keeps the history","maintainers":[{"name":"dennisrongo","email":"dennis@menacestudio.com"}],"readme":"# dsh-plan-board\n\n**npm:** [`@dennisrongo/dsh-plan-board`](https://www.npmjs.com/package/@dennisrongo/dsh-plan-board)\n\nPlans that outlive the scrollback. Every plan the agent presents through `exit_plan_mode` is written to `<workspace>/.dsh/plans/` as markdown, a window opens so you can read it at full size, and a **Plans** tab keeps the history with each plan's outcome.\n\n## Two ways a plan gets captured\n\n**Explicitly — plan mode.** `/plan`, the model explores, then presents through\n`exit_plan_mode`. The plan is stored with the real review outcome: *Approved*,\nor *Kept planning* with the reviewer's own words.\n\n**Implicitly — a fenced block.** Most sessions never enter plan mode, and the\nmodel just writes the plan into the reply. So the plugin registers a\nsystem-prompt section asking it to wrap plans in a fence:\n\n    ````plan\n    # Title of the plan\n\n    ...the complete plan as markdown, including any ``` code blocks...\n    ````\n\n**Four backticks, not three, and that is load-bearing.** A plan is a design\ndocument, so it routinely contains code blocks — and a three-backtick plan fence\nends at the first ``` inside it. That is CommonMark, not a parser bug, and it\nshipped: two plans captured from a real session were cut at their \"## The\nprompt\" heading, losing the prompt template and everything after it. The panel\nrendered the truncation faithfully, because by then the data was already gone.\nThe extractor now honours fence length (a fence closes only on a bare fence at\nleast as long) and skips nested blocks that carry an info string, so a\nthree-backtick plan containing ```ts still survives. The one case nothing can\nrecover is a bare ``` inside a three-backtick plan — nothing distinguishes it\nfrom the plan ending — which is exactly why the instruction asks for four.\n\nAnything inside that fence is captured as a plan, status **Proposed** — no\nreview was raised, so it is not called \"Awaiting review\". Both routes land in\nthe same store and the same panel.\n\nThis is a **marker, not a heuristic**, and that is the whole design. Sniffing\nassistant prose for \"plan-shaped\" structure fires on any answer with a heading\nand a list; a plan store full of false positives is worse than one that\noccasionally misses. When the model does not fence, nothing is captured and the\nplan stays in the transcript — which is exactly the behaviour without this\nplugin.\n\n**Manually — Pin as plan.** For that case, every assistant message carries a\n*Pin as plan* action. The whole message becomes the plan, because without a\nfence there is nothing to trim by and guessing where the plan starts would be\nthe heuristic this design avoids.\n\n## Why this exists\n\ndsh already has plan mode, and it is good: `@deepseek-ai/dsh-plan-mode` logs a `plan/mode` event that survives resume and fork, registers `/plan`, and exposes an `exit_plan_mode` tool that presents the complete markdown for Approve / Keep planning.\n\nWhat it does not do is **keep** the plan. The markdown exists only inside the tool-call event in the session log — scroll past it and it is gone, it is not a file you can diff or commit, and the reviewer's feedback exists only as the text of a thrown error. A plan is the most reviewed artefact in the whole session and it was the least durable.\n\n## What you get\n\n**A panel that docks beside the chat when a plan is presented.** It opens by itself, spans the full window height, takes the right half of the conversation column and *pushes the chat aside* rather than covering it, and closes on Esc or the X. Closing is per-plan, so dismissing one does not suppress the next, and the chat gets its full width back the moment it closes.\n\n**Approve, Keep planning and Revise, in the panel.** While the review is live the footer carries the same decision the conversation card offers, plus one it does not: **Revise** turns the plan into an editable buffer and sends your edited version back as review feedback, so the model re-presents it instead of you retyping the changes into the composer. Answering in either place settles the same review.\n\n**A Plans tab** beside Chat, Trajectory and Todo: every plan for the workspace, newest first, each with a status pill — *Awaiting review* · *Approved* · *Kept planning* — and, on a rejected plan, the reviewer's own words.\n\n**Files you can use.** `<workspace>/.dsh/plans/20260829T121500123-add-a-hook-lifecycle.md`, markdown with a small metadata block:\n\n```\n---\nid: \"20260829T121500123-add-a-hook-lifecycle\"\ntitle: \"Add a hook lifecycle\"\nsessionId: \"...\"\ncreatedAt: 1756470900123\nstatus: \"approved\"\ndecidedAt: 1756470960000\n---\n# Add a hook lifecycle\n...\n```\n\nThe metadata is JSON-per-line rather than YAML. It reads the same, but the writer cannot produce something the reader mis-parses — and it has to survive a model-written title and free-form human feedback containing quotes and newlines, which is exactly where a hand-rolled YAML subset eventually breaks.\n\n## What it will not do\n\n**It does not take over the question service.** `exit_plan_mode` presents the plan through `ctx.userQuestions.ask()`, and that service documents **one active provider per context** — the shipped question UI holds it. Registering a second provider to get Approve buttons would hijack every question in the harness, not just plan reviews, so this plugin never does.\n\nIt does not have to. A raised question reaches the browser as a pending-interaction carrier on the session's conversation snapshot, and the *carrier* owns the answer, not the provider. The panel reads `sessions.binding(id).session.getSnapshot().pending`, narrows it with the same rules the shipped decision card applies, and calls `respond()`. That makes it a second remote control for one specific wait: the conversation card keeps rendering and keeps working, whichever surface answers first settles the review, and the other one's receipt comes back `not-pending` — reported in the footer, never swallowed.\n\n**It cannot approve an edited plan**, and that is the harness's rule rather than a shortcut here. `exit_plan_mode` checks for free-text feedback *before* it looks at which button you pressed, so any edit reads as \"keep planning\" whatever label rides with it. Revise therefore sends the edited plan back as feedback and asks for it to be presented again — the plan the agent carries out is always one it has actually seen.\n\n## How it works\n\nThe capture point is `tools/execute`, the around-dispatch waterfall. `next()` runs the tool body — the call that blocks on the human — so wrapping it is what makes the *outcome* observable: the plan is written `pending` before `next()`, and the same file is settled to `approved` or `rejected` after, with the rejection feedback lifted out of the error the tool threw. `tools/pre-execute` was the obvious alternative and sees the plan but never the outcome.\n\nThe dock is a `shell.overlay` entry, not a `conversation.view` tab, and that is not a style choice. Views are rendered one-at-a-time by the session body (`only: <active id>`), so an inactive tab is not mounted and cannot open itself when a plan appears. An overlay is shell-scoped and always mounted, so \"show the plan the moment there is one\" is something this plugin can actually guarantee. The tab exists too — it is the history browser, opened by hand.\n\n`shell.overlay` is not a layout sibling of the chat, so the panel cannot simply occupy half a column: it is `position: fixed`, measures the conversation column, and applies an inline `padding-right` to push it aside by exactly the dock's width. Three details make that survivable. It anchors on **`[data-slot=\"conversation\"]`** — slot names are the documented plugin API, while the class names beside them are hashed CSS-module identifiers that change on any harness build. The padding is applied **inline**, because the column's own class selector has the same specificity as an attribute selector and which stylesheet lands last is not this plugin's to decide. And a `MutationObserver` re-applies both if a React re-render drops them, so the failure mode is a moment of overlap rather than a chat stuck at half width. If the column cannot be found at all the panel still renders against the viewport edge and simply overlays — a plan you can read on top of the conversation beats no plan.\n\nSharing the shell's top band with other overlays took two attempts. The first\naligned the dock below the tab strip by measuring\n`[data-slot=\"conversation.view\"]`, which reviewed well and was wrong: that\nslot's first child is the Chat view root *inside* the scrollport, so its top\ngoes negative as soon as the conversation scrolls — -1748px on a 2342px chat in\na 594px scrollport. The guard rejected the negative and fell back to the column\ntop, silently restoring the geometry the anchor existed to remove, and the\nheader went back under `dsh-weather`'s bar with Copy and Close dead. The panel\nnow spans the full window and *claims* its strip instead: it marks itself\n`data-dsh-overlay-claim=\"right\"`, and the weather bar centres on the span that\nis left rather than on the viewport, so it slides aside and sheds detail\ninstead of being painted over. Mission control needs no such marker — its rail\nreserves itself by padding the shell frame, which the dock already clamps to.\n\nOne thing z-index cannot fix: DSH Desktop on Windows lays a 36px window-drag\nstrip across the top of the viewport, and the compositor resolves a drag region\n*before* hit-testing, so it swallows clicks whatever sits above it. The panel's\nbackground still spans the full window; its header is inset out of the strip.\n\nFreshness rides a **change token**, the same shape `dsh-git` uses, for the same reason: the UI needs to notice a new plan without re-reading everything. Here the token is a plain in-memory counter, because this process is the only writer — no `fs.watch`, no handle per workspace. Polling stops while the document is hidden and re-reads immediately on focus, so a background tab costs nothing.\n\nPlan bodies never ride the list. `list` returns metadata only and `get` fetches one body at a time, so a workspace with 200 plans does not ship a megabyte of markdown to draw a sidebar.\n\nMarkdown is rendered into **React elements**, never `dangerouslySetInnerHTML` — a plan is model-written text that may quote something that came off the internet, and building elements means there is no escaping step to get wrong. Links render as `label (url)` rather than as anchors, so a model-authored destination is inspectable and inert.\n\nThree storage details are deliberate. Writes go through a temp file and `rename`, which is atomic within a filesystem, so a crash mid-write leaves the previous plan intact rather than a truncated one — the plan you are about to approve is exactly the file that must not be half-written. Ids coming off the wire are validated against a strict slug pattern before they are ever joined into a path. And pruning keeps the newest 200 plans **plus every pending one**, whatever its age: a pending plan is one nobody has answered yet, and deleting it is deleting live work.\n\n## Endpoints\n\n| Method | Takes | Returns |\n|---|---|---|\n| `dshPlans/list` | `{ workspaceId }` | `{ plans, token }` — metadata only |\n| `dshPlans/get` | `{ workspaceId, id }` | `{ plan? }` — with the markdown body |\n| `dshPlans/changeToken` | `{ workspaceId }` | `{ token, pendingId? }` — the polled endpoint |\n| `dshPlans/discard` | `{ workspaceId, id }` | `{ ok, token }` |\n| `dshPlans/pin` | `{ workspaceId, messageId }` | `{ ok, id, token }` or `{ ok: false, reason }` |\n\nAll take a single parameter named `request`, and each reply is an **envelope** — `{ ok: true, value }` or `{ ok: false, error }` — never the bare payload.\n\nTwo naming constraints are not obvious and both fail silently. A method may not be called `remove` (nor `has`, `install`, `installDirect`, `installScoped`, `ctx`, `empty`, `invokeRemote`, `methods`, `name`, `namespace`): the client's `RemoteNamespaceService` already owns those, and `$mount` **throws** on a collision, so the namespace never appears and every seat the plugin registers quietly fails to exist. And a client that reads the payload directly instead of unwrapping `value` gets `undefined` from a promise that resolved successfully, so the view sits on its loading state with no error to show. The smoke test pins the first; the client's types pin the second.\n\n## Install\n\n```bash\ndsh plugin add @dennisrongo/dsh-plan-board\n```\n\nRestart the profile afterwards — the Typert loader caches its per-package verdict for the process lifetime, so a newly added service is not picked up by a browser refresh.\n\n## Requires\n\n`ctx.tools` and `ctx.workspaceRegistry`, both composed by `@deepseek-ai/dsh-web-app` by default, and `@deepseek-ai/dsh-plan-mode` on the agent roster — without plan mode there is no `exit_plan_mode` to capture, and the plugin sits inert rather than failing.\n\n---\n\n## About the author\n\nPlugins and walkthroughs on [YouTube @codingmenace](https://www.youtube.com/@codingmenace) —\nI build AI coding tools in public, including DeepSeek Harness itself. More at\n[dennisrongo.com](https://dennisrongo.com/).\n","readmeFilename":"README.md","keywords":["ai-coding-agent","claude-code-alternative","deepseek-harness","dsh","dsh-plugin"]}