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 browser stops sending
dragoverwhen content churns under a still cursor, which cancelled the
drop entirely (#105). - A card dragged toward a column off the side of the window carries the
board with it, both axes at once from one held cursor, with the edge zones
measured from the window the reader can actually reach — which matters
under large default fonts and narrow windows, where the document itself
scrolls sideways (#108). - The board's header holds its shape: Board and Statuses sit by the heading
as links, the display controls are labeled toggle switches with pinned
widths, and the updated-time no longer shoves them around as it changes
(#107).
Compatibility across versions
- History written by a newer Workbook is refused with an upgrade signal
instead of a corruption claim. Reads still serve the task from its stored
checkpoint with an advisory, mutations of that one task are refused with
the newnewer-writercategory and exit 9 saying to upgrade, and fetch,
sync and push keep working — nothing wedges, other tasks are unaffected,
and unpublished local work publishes itself on the first sync after
upgrading (#97). Assignments, comments and attachments are the first
writers to raise the format generation, so this boundary is now guarded in
both directions going forward. A clone still on v0.4.4 predates the signal
and reports a task carrying them as corrupt-data — which an upgrade, not a
repair, resolves; tasks without them stay byte-identical everywhere.