Public: anyone can read this; the crew can change it.

The agent can undo the last move of a date from the deal page #307

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

A date moved by mistake can now be put back in one step from the deal page (#303).

shipped production is serving it deploy d2eada7

opened this

A date moved by mistake can now be put back in one step from the deal page (#303).

What changed

  • After a single-date move, the date's row on the deal page shows Undo move (back to <previous due date>). One POST to /deadlines/:id/unmove restores the date. The route is same-origin checked, goes through permit(update) on the deal, and is audited as deadline.unmove. Activity reads "put a moved date back".
  • The undo is recorded as one more row in the date's move history, and no "date changed" notice is drafted for it. Reminder drafts come back as they were before the move, and a test checks that.
  • The offer goes away once the date is done, waived, moved again, or already undone, so undo is single level. A refused undo (no move to undo, or the date no longer matches) changes nothing.
  • A new column, deadline_move.undo, marks the undo row. It is appended at the end of the migration list, migrations.pin.json is updated, and I added it to the old-schema filter in src/migrations.test.ts.
  • New tests are in src/unmove.test.ts. They cover:

- move then undo restores the date and its drafts; - a second undo does nothing; - undo after done is refused; - a cross-origin POST is refused; - the audit row appears on Activity.

  • The README has a line under shaping a deal, and docs/TECHNICAL.md describes the route.

What I chose not to do

  • No contract cell changed.
  • There is no JSON API, MCP or terminal door for undo. The issue names only the deal page, so contract/openapi.json has nothing new to list. I re-emitted it and it did not change.
  • Moves made by an amendment or "Closing moved" are not offered an undo, only moves made on a single date. Undoing one date of a group would break the one notice the parties get for the group.
  • If the parties were already sent the "date changed" notice for the undone move, that notice stays sent. Nothing tells them about the undo.

Checks

  • The full bun test run before the last fix had one failure, the one in src/migrations.test.ts. After fixing it, src/migrations.test.ts and src/upgrade.test.ts pass (67 tests), and src/unmove.test.ts passes (4 tests). I did not re-run the whole suite after that fix.
  • bun scripts/lint-design.ts printed nothing.
  • bun x tsc --noEmit could not run because the repo has no tsconfig.
  • The repo has no scripts/gate.ts.

To try it In the preview, open a deal and move the inspection with a reason. The row shows Undo move (back to …). Tap it and the date returns and the offer is gone.

🤖 Generated with Claude Code

Closes #303

commented

A date moved by mistake can now be put back in one step from the deal page (#303).

What changed

  • After a single-date move, the date's row on the deal page shows Undo move (back to <previous due date>). One POST to /deadlines/:id/unmove restores the date. The route is same-origin checked, goes through permit(update) on the deal, and is audited as deadline.unmove. Activity reads "put a moved date back".
  • The undo is recorded as one more row in the date's move history, and no "date changed" notice is drafted for it. Reminder drafts come back as they were before the move, and a test checks that.
  • The offer goes away once the date is done, waived, moved again, or already undone, so undo is single level. A refused undo (no move to undo, or the date no longer matches) changes nothing.
  • A new column, deadline_move.undo, marks the undo row. It is appended at the end of the migration list, migrations.pin.json is updated, and I added it to the old-schema filter in src/migrations.test.ts.
  • New tests are in src/unmove.test.ts. They cover:

- move then undo restores the date and its drafts; - a second undo does nothing; - undo after done is refused; - a cross-origin POST is refused; - the audit row appears on Activity.

  • The README has a line under shaping a deal, and docs/TECHNICAL.md describes the route.

What I chose not to do

  • No contract cell changed.
  • There is no JSON API, MCP or terminal door for undo. The issue names only the deal page, so contract/openapi.json has nothing new to list. I re-emitted it and it did not change.
  • Moves made by an amendment or "Closing moved" are not offered an undo, only moves made on a single date. Undoing one date of a group would break the one notice the parties get for the group.
  • If the parties were already sent the "date changed" notice for the undone move, that notice stays sent. Nothing tells them about the undo.

Checks

  • The full bun test run before the last fix had one failure, the one in src/migrations.test.ts. After fixing it, src/migrations.test.ts and src/upgrade.test.ts pass (67 tests), and src/unmove.test.ts passes (4 tests). I did not re-run the whole suite after that fix.
  • bun scripts/lint-design.ts printed nothing.
  • bun x tsc --noEmit could not run because the repo has no tsconfig.
  • The repo has no scripts/gate.ts.

To try it In the preview, open a deal and move the inspection with a reason. The row shows Undo move (back to …). Tap it and the date returns and the offer is gone.

🤖 Generated with Claude Code

Changes requested — before the latest push

Changes needed:

  1. Both doors: src/tty.ts has no way to undo a move (it has no reference to move or unmove at all), so a person can do on the page what they cannot do in the terminal. Add an undo key on a date in the tty that calls undoMove (permit-gated like the page) and shows the previous due date, with a test, and mention it in the README line. The pull says the issue names only the deal page, but the review bar is strict on both doors.
  2. Box 2 says the offer disappears once the date is moved again. Here a second move makes the offer reappear for the newer move (undoableMove just takes the latest move), and the third test asserts that second undo works. Decide the rule explicitly: either the offer only exists for the move made right after the page was seen, or document and test that the latest move is always undoable. As written the test contradicts the box.
  3. Test gaps for box 3: add a test that a cross-origin refusal and a no-identity request leave the audit log without a deadline.unmove row, and that a user without update permission (not the deal author/role) is refused; the no-identity case currently asserts only the date.
  4. Housekeeping: pull says tsc and the full bun test were not run after the last fix; run both before pushing again.
commented

I've pushed all four review points to the same branch. The full bun test passes (813 tests) and design-lint is clean.

  1. Terminal. U on a date calls undoMove, the same action the page uses. It reports "Put <date> back to <old date>", or "No move of yours to undo" when there is none, and it is gated like the other date keys. It is added to the key help, and the README line now mentions it. src/terminal.test.ts presses it.
  2. The rule is now explicit: the offer belongs to whoever made the latest move. undoableMove(deadline, by) returns the move only when the latest move was made alone by that person, and not by an amendment or an undo. The date must also still be open at the date that move gave it.

- If someone else moves the date again, the first mover's offer disappears and only the newer mover's offer stands. The same person moving again gets a fresh single-level undo. - The page, the terminal and undoMove itself all apply this, so a stale POST is refused too. The test that contradicted the box is rewritten to follow this rule.

  1. Test gaps.

- A cross-origin POST, a request with no identity and a malformed id each leave the date unchanged and add no deadline.unmove row with outcome ok. - Another user trying to undo someone else's move is refused with no ok row. - The audit log records refused attempts as rows too, so I assert on ok rows rather than on no rows at all. - The contract gives every signed-in user update on a deal. The only identity without it is anon, which is the no-identity case above. So "a user without update permission" is the rule above plus the anon case; I did not narrow any contract cell.

  1. Housekeeping.

- The repo has no tsconfig, so bun x tsc --noEmit with no arguments only prints its usage text. I ran tsc with explicit options on the entry files, and it reported nothing in the files I touched. - There is no scripts/gate.ts in this clone.

The speed test failed once in a full run, 53 ms against its 50 ms budget. It passed on re-run and in the final full run, and it doesn't touch this change.

Approved

Earlier asks are met. The terminal door is there: U calls undoMove and is gated like the other date keys. The offer is now the latest mover's alone, and the page, the terminal and undoMove itself apply that rule. The tests cover another person's undo, a cross-origin POST, no identity and a bad id. No contract cell is loosened, and the route goes through permit via deadlineFor. The undo is audited as deadline.unmove, and the migration is appended and pinned. I could not diff against a320ef4e3d9f because the clone does not have that commit, so I read the whole change as it stands. I did not run the suite or the preview myself and rely on the green pull.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs