Releases: dgoings/workbook
Release list
Workbook v0.5.1
Five stories from adversarially reviewed pull requests: the web board learns
to edit assignments and accept pasted images, and three defects found in
earlier reviews are closed.
Added
- Assignments can be added and withdrawn from the web board, through the same
claim semantics the CLI uses (#121). - An image in the clipboard pastes straight onto the attachment controls, with
a generated filename and the same ceilings as drag-and-drop (#124).
Fixed
- With no sync watcher answering, the board's Publishing switch shows a
stalled state naming why, instead of silently flipping server configuration
it cannot apply (#123). - Terminal output neutralizes Unicode bidi-control format characters, so a
hostile title or comment can no longer visually reorder a rendered line
(RTLO spoofing); benign format characters like emoji ZWJ pass through
(#122). workbook validatenow refuses a history containing duplicate operation
ULIDs as corrupt-data, and the projection reports the offending task instead
of crashing with a raw SQLite constraint error (#120).
Workbook v0.5.0
A project now owns its statuses, tasks carry comments, attachments and
assignments, deleted tasks come back through the board, and project identity
no longer depends on what a branch happens to contain — from thirty reviewed
pull requests.
Project identity
- Project identity moves out of branch content into
refs/workbook/project.
A checkout taken before a project adopted Workbook carried no
.workbook/config.json, so bootstrap minted a second identity and the
private guard then rejected the real configuration on every command; refs
live in the common Git directory, so nothing about a branch can strand
identity anymore. The ref is written deterministically, so two clones
adopting the same project converge on the same commit, and a disagreeing
private guard is now repaired from the ref instead of wedging the
repository (#81).
Per-project statuses
- The status vocabulary becomes a modeled document, and status validity moves
to the mutation boundary. A status stored in history is never rewritten,
only resolved through the vocabulary on read — so every task ref already
written stays byte-identical, a property captured in a golden test against
the previous release before any change landed (#82). - The vocabulary lives in
refs/workbook/config, a configuration ledger with
a Git history of its own, synchronized and reconciled the way task refs
are: a rename made in one clone arrives in the next as an operation, not a
file merge, so concurrent changes converge or surface as domain conflicts
instead of conflicting a JSON file (#83). workbook statusgives a person and an agent the vocabulary:list,
add,rename,label,move,tag,untag,delete --into, and
log, whose every entry prints the exact inverse command that would undo
it. Deleting a status requires a destination, so no task is ever stranded
by a vocabulary change (#84).- Both boards build their columns from the project's own vocabulary. A
project that renamedreadytotodoused to get a READY column with the
task missing from it; a stored status that a rename or removal forwards is
now drawn in the column it now means, and only a status that forwards
nowhere lands in the unknown region. The terminal's wide-layout threshold
becomes a per-column budget, so a three-status project reaches its wide
layout on a terminal that could never have fitted six (#85). - The generated guidelines describe the statuses the project actually has:
the table gains position and tag columns with a legend saying what each tag
makes the machine do, and the lifecycle prose is read off the tags rather
than off hardcoded names (#86). A display label carrying an HTML comment
opener can no longer swallow the rest of the generated document, and the
migration note about a retired default column stops nagging a project that
deliberately added the column back (#90). - A new project no longer gets a Blocked column. Tasks have dependencies, and
a column duplicating that claim is a second answer to the same question.
Nothing is taken from existing projects: a project that already has
blockedkeeps it until a person runs
workbook status delete blocked --into <status>— this repository ran
exactly that migration (#87, #88). - The statuses can be administered from the browser at
/statuses: add,
rename, relabel, retag, reorder by drag or by buttons, and delete into a
chosen destination, with each change reporting how many tasks moved and how
many became claimable. Every mutation names the columns the caller actually
saw and is refused as stale rather than merged when they have changed; the
board's columns are never rebuilt live under a reader — a standing notice
offers the reload. The first cut opened as a half-height sliver over the
board and became a page of its own before release (#92, #93, #94). The
reorder answers a drag ondragenteras well asdragover, so a browser
engine that switches events when content moves under the cursor still finds
a panel that answers, and the drop mark survives the leave-event churn a
cursor crossing child elements produces (#110). - A task stranded on a status that resolves nowhere can be dragged back onto
the board: the web unknown-status region's cards are draggable out into any
column,status listbounds its unresolved listing to the first ten IDs
while keeping the count exact, and the contract is written down — an
unrecognized status is a mutation refusal, never a corruption claim (#89). - The web board's columns were sized by a hardcoded six-track grid, so a
five-column board sat frozen at minimum width until the viewport reached
about 1640px while a phantom sixth track absorbed the free space. The board
now creates exactly one track per rendered column, growth begins as soon as
the columns can use the space, and columns stop growing at 26rem so a wide
monitor no longer stretches every card across the page (#91).
Deleted tasks
- Deleted tasks are a hideable "Deleted" column at the end of the board
instead of a separate page. The header link toggles/?deleted=1, so the
state is bookmarkable and Back works; the column lists tombstones
newest-first with a Restore button on each card, and dragging works both
ways — a deleted card dropped on a status column is a restore naming its
destination and position, and a live card dropped on the Deleted column is
a delete. The/deletedpage is gone (#96). workbook restoregained--into <status>, so a deleted task can come
back into a chosen column instead of the one it died holding, recorded as
one history entry; the web restore accepts the same choice (#95).
Comments, attachments and assignments
- Tasks carry a comment thread and an attachment list — comments that can be
added, edited and removed, attachments that are an uploaded file's bytes or
a link. Concurrent edits converge, and a task with neither stores exactly
the bytes it always did. Ceilings are asked as growth — 16 KiB per comment,
500 comments, 50 attachments, 1 MiB per file, 10 MiB of live files per
task — so a task carried over a limit by a teammate's change can still be
edited and, above all, shrunk back under it (#99). workbook updatecomposes them into single commits:--comment,
--edit-comment,--remove-comment,--attach-file,--attach-urlwith
--attach-label, and--remove-attachment, alongside--statusand the
rest;workbook showprints the thread and the list, and
show --get-attachmentwrites a file's bytes out. Comment and attachment
IDs take any unambiguous prefix, the same contract task IDs have (#100).- Tasks can be assigned. An assignment names a principal with an optional
agent label and records who assigned it and when; two clones assigning the
same task both survive as a legible both-assigned state, and an assignment
can only be removed by the person it names or whoever recorded it — a
removal by anyone else is recorded and changes nothing, on every clone
(#98).--assign selfclaims in one atomic commit with a status change,
workbook nextskips tasks held only by others and says so when everything
eligible is held,next --claimpicks and assigns in one step, and
assigning over somebody else's hold is refused — exit 10, naming the holder
and--force, which records the assignment beside theirs (#101). - The web task page gained the comment thread and the attachment list —
add, edit, remove, upload, link, download. These panels are deliberately
not optimistic: identity and attribution come from the recorded operation,
so each panel disables, sends, and draws the answer. Only GIF, JPEG, PNG
and WebP are served inline; every other type, including every spelling of
SVG, downloads as an opaque attachment (#102). The board and task page also
show who holds a task — assignee chips on held cards, an Assignments
section on the task page — derived through the same functions the terminal
prints, so the surfaces cannot drift (#104). - The New Task form stages attachments before the task exists. Files and
links are checked against the same ceilings the server enforces before
anything uploads; a create whose attachments partially fail holds the form
with per-row reasons and a Retry attachments button bound to the task it
created, and the panel refuses changes while a run is walking it (#106). - Both attachment surfaces accept drag and drop, through the same pre-checks
as picked files. A refused file says why while the drag is still over the
zone — a refused drop is cancelled by the browser and never delivered, so
the drop handler was never the place to say it; a dropped folder is named
rather than staged and failed; and a file dropped near the board can no
longer move an unrelated card, nor navigate the page away and destroy
staged work (#109).
Markdown
- Task descriptions and comment bodies render as markdown in the browser:
headings, emphasis, code, lists, blockquotes, http and https links, and
images. The description opens rendered with an Edit control that swaps the
textarea in. Images are writtenand resolve only
against the task's own attachments — every other image target, including
external URLs, is drawn as text, so a board never reports its readers to
whoever wrote a task. Anything outside the subset is drawn as the
characters that were typed (#103).
Web board drag and scroll
- A board column taller than its viewport could only be reordered a few
places per gesture. A column now scrolls while a card is held near its
edge, ramping with depth, and keeps scrolling at full speed when the card
is pushed past its end; the drop line is recomputed as the cards move, so
the card lands where the line shows. This round also fixed drops being
silently lost after a column had autoscrolled — the b...
Workbook v0.4.4
Joining an existing project no longer forks it, the board tells the truth about
what it could and could not do, and the measurement harness stops leaking the
processes it prices — from fourteen reviewed pull requests.
Setup and project identity
workbook setupasksoriginbefore minting a project identity. A checkout
cut before the project adopted Workbook carries no tracked configuration, and
setup used to read that as a brand-new project — splitting the repository
into two identities and wedging it behind a guard mismatch that no
working-tree cleanup could reach. Setup now adopts the configuration
committed on origin's default branch, stops rather than guessing when origin
holds task refs but no committed configuration, and treats an unreachable
origin as an error instead of minting blind;--no-syncremains the
deliberate local-only bootstrap. The guard-mismatch error names both files,
both identities, and the recovery, which is safe because the projection
validates its stored project ID and rebuilds itself (#66).
Web board
- A refused write is no longer re-based onto a head the server just refused.
When the forced refresh after a stale-write refusal cannot read the board,
the queue stops and reports instead of sending every queued intent to the
same refusal; when only the relationship context was superseded but the
board read landed, the queue re-bases and proceeds. The outage message
appears only when the read genuinely failed (#70). - A detail save whose form was left before it landed reports its outcome
instead of being swallowed. The refusal is named in the notice and staged on
the task's own form, the server's reason lands last, and a save that lands
while detached no longer yanks the reader back to the board — which used to
silently destroy a New Task draft being typed (#73). - An editor whose task was deleted elsewhere is told so. The refused-save
message used to invite a doomed retry against a version the server no longer
holds, and the failure notice linked to a task page that no longer exists;
both now state the deletion, and the edits stay in the form to copy out
(#76). - The withdrawal that corrects an open form after a refused board change no
longer claims the unedited fields show the server's version when the forced
refresh could not read the board — the form and the card are now written
against the same fact (#78). - A new task drafted in a form the reader left before the create landed is no
longer lost in silence. A refused detached create reports into the notice
with a Restore draft button that brings back every typed field — saying
plainly that staged relationships are not restored — and a create that lands
while detached leaves the reader on the route they chose (#79).
Command line and sync reporting
workbook config unset --jsonreports"command":"config unset"instead of
claiming to beconfig set, and a test holds the two verbs apart so they
cannot drift back together (#67).- An ignored ref under origin's task namespace now reaches the surfaces where
a user actually meets one:workbook setupnames the skipped refs beneath
itsSync:line, an inline mutation's report carries them, and the sync
watcher announces each newly skipped ref once on its terminal and carries
the set inworkbook sync --status, text and JSON alike. The report keeps
its restraint: removal advice is offered only for names no project's ID
format could have produced (#69).
Performance harness and targets
- The streaming history read's memory bound is pinned by a test. A rewrite
that buffers the whole corpus before delivering it — the exact regression
the streaming work exists to prevent — used to leave the suite green and
was caught only by an off-repo bench run; it now fails an allocation
ceiling asserted on settled live heap (#68). - The local-bare sync scenarios' tracking-ref reset was filed for deletion as
defensive dead code and turned out to be load-bearing: each sample's second
measured sync populates the tracking namespace, and leaving it uncleared
inflates the initial-sync measurement by about a quarter at 500 tasks. The
reset is kept, pinned by a test that fails without it, and documented with
the measurement (#71). - The measurement harness reliably reaps the processes it spawns. The helper
that priced commands used to signal their process group only on
cancellation, so a command that exited normally could leave a busy-loop
descendant spinning at a whole core for days — observed once for a week.
Every measured group is now killed after its sample is stamped, at the
shared helper and at every remainingSetpgidexec site, and the tests
reap their own helpers even if the test binary dies mid-run (#72, #77). - Read-path scenarios carry approved duration targets:
cli-listjoins
cli-showon the 200 ms local budget, fetch-first reads likecli-next
carry the 1,000 ms synchronized budget as recorded policy, and the stale
100 ms warmapi-updatefigure in the README now reads 150 ms everywhere
it speaks in the present tense (#74, #75).
Workbook v0.4.3
Reports you can actually read — on the board, on the terminal, and about an
ignored ref — plus a hardened watcher socket and a stricter board Host check,
from thirteen reviewed pull requests.
Web board
- A refused change is now reported on the card it concerns and stays there until
you dismiss it, change that card again, or the board stops carrying the card.
The one-second poll used to erase the banner a refused drag wrote before
anyone could read it; when the card leaves the board the report moves to the
notice above it and names the task (#55). - A refused board change no longer discards what you have typed into an open
detail form. Untouched fields follow the task the board holds, edits in
progress stay as typed with the caret where it was, and a save in flight still
reports into the form (#61). - A card leaving a column no longer disturbs the cards below it. Departed cards
are swept before the columns are reconciled, so another clone moving one card
out of a long column no longer re-inserts — and blurs — every card under it
(#57). - The board and the task form are more compact: columns hold a minimum width and
the board scrolls sideways instead of squeezing titles over four lines, column
headers drop therefs/workbook/status/…line, card titles are no longer
underlined, the Depends On and Blocks groups and the form footer are shorter,
Create more sits directly above Save, and the Labels caption is aligned
with its input (#50).
Command line
workbook showkeeps a description's line structure instead of collapsing it
to one line. Later lines are indented by a tab, which preserves the guarantee
the collapse provided: no description line can be read as one ofshow's own
fields (#54).workbook servesays why the board moved when the default127.0.0.1:7331is
taken, naming the collision on its own line above the board banner instead of
falling back silently. An explicit--addrstill fails rather than moving
(#53).fetch,push, andsyncno longer end an ignored-ref report with blanket
git push origin --deleteadvice. Each ignored ref is classified on its own
line asno project's taskormay be another Workbook's task; every ref is
reported as kept onorigin, and the removal command is offered only for names
no project's ID format could have produced (#62).
Hardening
- The sync watcher's Unix socket is bound in a private per-user directory rather
than world-writable/tmp. Candidate directories are refused unless they are
real directories owned by the caller and writable by nobody else, the socket is
created0600with no world-connectable window, both ends of the watcher
protocol bound the line they read, and the watcher bounds the status it serves.
Exclusivity survives a change of socket path, so an older watcher still keeps
ownership across the upgrade (#51). - The board's
Hostcheck now pins the bound address on an explicit non-loopback
bind —--addr 192.168.1.5:7331is named by that address alone — and the
Origincheck is measured against the bound address too, so a page that
rebinds its own DNS name to a LAN board no longer holds same-origin read and
write on it. A wildcard bind is the one case with no host to pin; the exposure
warning now names drive-by access from off the network, and the README says so
where the claim is made (#58).
Agent skill and tests
- The Workbook agent skill says where a dependency's title comes from: resolve
eachdata.dependenciesentry withworkbook show <id> --json, never invent a
title, and report a dependency this clone cannot read by ID instead of
abandoning the task. Bad news — a blocked task, a failed command — is announced
by title like everything else. The behavioral run behind the change is recorded
underdocs/superpowers/evidence/(#52). - A capability probe that skips without the marker is now detected structurally
rather than by convention, so coverage cannot quietly disappear on a machine
missing a tool while CI reports success (#59). - The board's card-signature fast path and the detail form's Blocks-edge
orientation guard are covered by tests that fail when the line they protect is
deleted; both previously left the package green (#56, #60).
Workbook v0.4.2
Quality and hardening for the web board, plus safety limits on task storage,
from nine reviewed pull requests.
Web board
- Saving a new task returns you to the board, and a Create more toggle
keeps a clean form open for the next task instead (#41). - New tasks land on the board optimistically: the card renders immediately
while the save is still in flight, and a refused save offers the draft back
instead of losing it (#46). - Card descriptions are hidden by default to keep columns scannable; a board
setting restores the previous behavior (#38). - Labels are edited as removable chiclets instead of one comma-separated text
field (#40). - Tasks whose status matches no column appear in an explicit unknown-status
section, matching the terminal board, instead of being hidden (#42). - The dependency search popup closes when it loses focus (#47).
Limits and hardening
- Task fields are bounded — title 500 bytes, description 64 KiB, labels
100 bytes each and 50 per task — and Workbook refuses to read a Git object
over 4 MiB, so one oversized task can no longer exhaust memory in every
clone. Web request bodies are capped at 1 MiB and the board server gains
connection timeouts. The limits are documented in the README (#44). - The web handler is built from a named options struct, and serve-level tests
fail if the delete/restore or depend/free wirings are ever swapped (#43).
Performance and tooling
- The benchmark harness measures the agent hot loop (
workbook next,
workbook show), warm board reads, and the sync watcher's steady-state CPU
and memory; a replacementapi-updatep95 target is proposed with evidence
underdocs/performance/(#45). go test -short ./...is the supported fast suite for local iteration and
parallel agents; the release and installer build tests share the ambient Go
build cache instead of cold-compiling everything per test (#39).
Workbook v0.4.1
What's Changed
- Stop board columns self-scrolling by disabling scroll anchoring on task lists by @dgoings in #23
- Add push and pull-request CI that cannot pass with tests silently skipped by @dgoings in #28
- Refer to tasks by title, not ID, when communicating with humans by @dgoings in #24
- Pick a free port automatically when the default board address is taken by @dgoings in #25
- Tolerate unrecognized refs under the shared task namespace by @dgoings in #30
- Sanitize control characters in text-mode task output by @dgoings in #26
- Reject cross-origin and foreign-Host requests on the web board by @dgoings in #27
- Fix web queue lost updates: detail form expectedHead and the dead stale-write re-base by @dgoings in #29
- Stop bare test origins from racing TempDir cleanup with background auto-gc by @dgoings in #31
- Reconcile the board renderer instead of replacing every card each poll by @dgoings in #32
- Complete POC acceptance coverage and documentation by @dgoings in #33
- Bound full history validation time and peak memory growth by @dgoings in #36
- Stop a merge to main from cancelling the previous merge's CI run by @dgoings in #34
Full Changelog: v0.4.0...v0.4.1
Workbook v0.4.0
What's Changed
- Add SessionStart hook for Claude Code remote sessions by @dgoings in #18
- Reconcile divergent task histories by replaying local operations by @dgoings in #17
- Implement task change history view by @dgoings in #19
- Add an optional sync watcher so mutations stop paying for synchronization by @dgoings in #20
- Show the task change history in the web UI by @dgoings in #21
- Render board mutations optimistically and publish them by nudging by @dgoings in #22
Full Changelog: v0.3.0...v0.4.0
Workbook v0.3.0
What's Changed
- Install stable and development Workbook side by side, publish Linux releases, and script the release process by @dgoings in #13
- feat: synchronize task mutations with origin automatically by @dgoings in #16
Full Changelog: v0.2.0...v0.3.0
Workbook v0.2.0
What's Changed
- feat: restore deleted Workbook tasks by @dgoings in #2
- feat: describe task operation commits by @dgoings in #3
- Split remote sync benchmarks by topology by @dgoings in #4
- Make default sync tip-focused and bounded by @dgoings in #5
- Add resumable semantic history validation by @dgoings in #6
- Improve responsive web board by @dgoings in #7
- Add web drag reordering by @dgoings in #8
- Correct local performance acceptance semantics by @dgoings in #10
- Copy task IDs from the web UI by @dgoings in #9
- Grow detail descriptions to fit the viewport by @dgoings in #12
Full Changelog: v0.1.0...v0.2.0