Repository navigation
Kotgent 0.8.0 — project delete and restore, and a board drag that shows the drop
Delete a project without losing it
A project used to be permanent. Creating one in the wrong checkout, or keeping one for a repository you
stopped working on, left it in every selector forever, because the only alternative was deleting rows that
carried real history.
kotgent project delete <uuid> — or Delete project in the Web UI's command palette — writes a
tombstone instead. The project leaves every selector and stops being a source of new work: task add and
task next refuse in its directory, and a kotgent start there is left unstamped. Everything else is kept
exactly as it was — the tasks, their order, their dependencies, their comments, and the sessions linked to
them.
.kotgent.json is never touched. That is deliberate, and it is why the delete needs no confirmation beyond
the dialog: the file stays committed, the directory keeps resolving to that project, and nothing on disk is
destroyed. Delete or move the file yourself if you want the directory to stop resolving.
Coming back is symmetric. kotgent project restore <uuid> brings the project and its whole backlog back, and
so does simply adopting the directory again with kotgent project init. kotgent project list --archived
finds the uuid of a checkout that no longer exists.
The tombstone is enforced at every layer rather than filtered in one, because a delete races everything that
was already in flight:
- Task creation into a deleted project is refused, not silently resurrected — including through a
deleted ancestor project, which a nested checkout could otherwise walk up to. task nextrefuses when the project is deleted mid-request rather than handing out work from a backlog
that is no longer live.- A tombstone that arrives from the daemon while a browser is mid-write is honoured all the way to the
board, rather than being overwritten by the response to a request that started earlier.
New routes: DELETE /api/v1/projects/{id} and its restore counterpart, with archived on the project DTO.
The board drag now shows where the card lands
The gesture already resolved the destination column and the insertion neighbour on every pointermove — and
drew neither. The column got a tint, the card that was about to move stayed put, and the landing position was
computed and never shown.
Now the other cards part to open the gap, a dashed slot marks the insertion point, and the dragged card
follows the pointer.
The invariant that makes it work is that layout never changes for the duration of a drag. Every movement
is a transform, the dragged card keeps its slot as an invisible in-flow placeholder, and the lifted copy is
position: fixed so the column's overflow cannot clip it on the way to another column. Column ownership is a
separate question from geometry and is answered by paint order through elementFromPoint, so an overlay such
as the open task panel cannot be dragged through.
Three things the drag did wrong are fixed with it:
- It swept a text selection across everything the pointer crossed.
pointermovecancelled its default
and the selection happened anyway, which puts the cause earlier: the compatibilitymousedownthat
pointerdowngenerates had already begun it. Cancellingpointerdownsuppresses that gesture and leaves
clickandcontextmenualone. - Columns never scrolled while dragging, so a drop position outside the visible slice was unreachable. A
frame loop now scrolls the column the pointer is actually over, at a velocity in pixels per second scaled
by elapsed time — a per-frame delta silently doubles on a 120 Hz display. - The lifted copy was reachable by keyboard.
aria-hiddenremoved it from the accessibility tree but
left its duplicated links in the tab order, so focus could land on a link assistive technology had been
told not to describe.inertdoes both halves.
The gesture is also bounded now: it ends through one idempotent abort covering pointerup, pointercancel,
a document-level lostpointercapture, an isConnected check in the frame loop, unmount, and a sidebar
toggle. Deleting the dragged task mid-drag used to strand the gesture, because the handlers lived on the
handle that went away with it.
Dialogs and requests that answer honestly
A submitted dialog that never hears back is worse than one that fails, and several paths could reach that
state.
- API requests could wait forever. Every request now carries a 60-second timeout, composed with caller
signals so unmount cancellation still wins. Uploads opt out, because a large file may legitimately take
longer. A timeout says the outcome is unconfirmed and stays retryable, rather than claiming a failure
it cannot prove. - Dismissing a busy create dialog reported nothing. Both the project and the task create paths now report
what actually happened to the submission they left behind. - A project archive refused late was dropped on the floor; it now reaches the board status.
- A failed refresh discarded a project mutation that had already succeeded.
- Foregrounding the app coalesces its project refresh instead of firing several, and archive derivations are
kept off live frames.
Versioned auth and hook routes
/api/v1/auth/{ticket,exchange,rotate} and /api/v1/hooks/* are the canonical paths. The former root paths
remain aliases, so hook scripts generated before the move keep working and nothing needs regenerating.
GET /auth stays at the root. Newly generated scripts and all bundled clients use the versioned paths.
Unlike 0.7.0's move, this one does not break older clients.
Smaller things
- Done sessions are ordered by recent activity, in both rows and groups.
- The sidebar header stays pinned above its content.
- Sidebar collapse moved off
⌘1, which belongs to tab switching, onto⌘.. - The board card's action menu is gone; the card opens the task.
- Phone board fixes: the column switcher is straight, every card stays inside its column, and the column is
sized by its cards rather than by a max-content row. - The task badge stays inside its session row.
- Unknown approval decisions decode instead of failing the read.
Full changelog: v0.7.0...v0.8.0
Upgrade
brew update
brew upgrade kotgent
kotgent installkotgent install is required, not optional. The launchd plist stores a version-qualified Cellar path, so
after an upgrade the installed service still points at the previous version's binary until the plist is
rewritten.