Skip to content

feat(trellis): trash projects in place with Undo - #16

Merged
Andrey170170 merged 7 commits into
dev_v2from
trellis-v2/c-trash-in-place
Oct 2, 2026
Merged

Andrey170170 merged 7 commits into
dev_v2from
trellis-v2/c-trash-in-place

Conversation

@Andrey170170

Copy link
Copy Markdown
Owner

Trellis v1 Stage C, Lane T3 step 5 (trashing projects in place with undo; v1 design M7).

Problem

Trashing a Trellis project meant: project settings → Move to trash → confirm → dropped back on the main screen, and restoring it meant a trip to Settings → Trellis (Andrei's "smoother project trash flow" note).

Change

  • No confirmation, no navigation, Undo instead. Moving a Trellis project to the trash (from its settings or the legacy sidebar's project menu) no longer asks first, and the page stays put. The toast has Undo. The project stays listed as In trash · Undo in the project picker (new sidebar), the project header (legacy sidebar) and its settings page until the page reloads. After that it lives in Settings → Trellis's trash as before. Undo restores the item and unarchives its conversations, as the trash's Restore does.
  • What still confirms: permanent T3-entry deletions (non-Trellis projects, "already gone" Trellis entries, mixed groups) keep their confirmations.
  • Server.
    • trashProject returns the Trellis item to restore (restore: {kind, id}; an idea restores as an idea, a fork as a workspace).
    • Catalog sync no longer deletes an empty T3 project whose item is in the trash. It stays hidden, and a restore reuses its id. Only graduated or purged ones are deleted. Without this, trashing an empty project deleted it, and the trashed state had nothing to show.
  • Web.
    • state/trellisTrashed.ts holds this client's trashed projects in phases (trashing → trashed → restoring), settled against each environment's Trellis status.
    • A status from before the trash, or a refresh that timed out, never drops a fresh entry.
    • A restore elsewhere (Settings → Trellis, the CLI, another client) drops the entry once a status shows it.
    • Undo keeps the row until a status shows the restore, so the picker's selection is not reset.
    • Project settings undo every trashed member of a group.
  • Docs: docs/user/trellis.md "Deleting and restoring" is updated.

Checks

  • Tests:
    • TrellisCatalog.test.ts: the trash result's restore target (idea, fork); a trashed empty project kept and reused, deleted once purged; no no-op retire actions for trashed empty projects or forks.
    • trellisTrashed.test.ts: phases against stale, fresh and restored statuses, other environments, and statuses that are not ready.
  • Visual check: a dev T3 against /trellis/dev-t3 with playwright-cli. Trashed bug4-repro from its settings: the page stayed, showing "In the Trellis trash · Undo" plus the toast with Undo. The project picker showed "In trash · Undo"; Undo there restored it, the row came back normal, and the selection was kept. A page reload without Undo drops the trashed row, as intended. Screenshots are kept locally: /tmp/stagec-trash-dev/*.png.
  • Local CI: knip:check (after removing an unused export), vp check, vpr typecheck and release-smoke pass. t3 tests fail only on the known CodexInstallation and AcpSessionRuntime.processTree failures. Package tests fail only on the known desktop libsecret and snapshot file-mode failures; client-runtime, relay and mobile pass when run separately. build:desktop fails on the known libsecret helper.
  • Reviews: Codex gpt-6.1-sol, then gpt-6-astra. Their findings are fixed: stale entries after a restore elsewhere, selection reset on Undo, stale statuses, grouped environments, settings undoing only one member.

🤖 Generated with Claude Code

Andrey170170 and others added 4 commits October 2, 2026 14:41
Moving a Trellis project to the trash no longer asks first or leaves the
page: the toast, the project's settings and its sidebar row offer Undo,
which restores the item and its conversations; the row stays listed as
trashed until the page reloads. The trash result names the Trellis item to
restore, and the catalog keeps an empty trashed project (hidden) so a
restore reuses it; only purged or graduated ones are deleted.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…listed until its restore shows

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…do covers every trashed member

An entry waits for a status showing its root retired before a live status
can drop it, so a status from before the trash or a timed-out refresh
never discards a fresh Undo; Undo keeps the row until a status shows the
restore. Every member environment's status is applied, and project
settings undo all trashed members.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-02T21:42:02.806826Z a3c7e1c New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L labels Oct 2, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 11887b70e2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

statuses.size === 0
? []
: projects.filter((project) => {
if (`${project.environmentId}:${project.id}` in trashedHere) return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep trashed projects out of new-thread targets

Returning false here keeps the locally trashed project in the shared useSidebarProjects() result, but that hook also feeds useHandleNewThread, the command palette, and the global new-thread default—not just sidebar rendering. If the trashed project is first in project order, the new-thread shortcut silently creates a draft against its removed workspace; users can also explicitly select it and hit the same failure. Preserve it for the Undo row without exposing it as a valid thread target.

AGENTS.md reference: AGENTS.md:L67-L70

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid: useSidebarProjects also feeds new-thread targets and the palette. Keeping a just-trashed project is now opt-in (keepTrashedHere), used only by the two sidebars; new-thread defaults, the palette and the home route no longer see it. Fixed in a52f45b.

Comment thread apps/web/src/hooks/useTrellis.ts Outdated
Comment on lines +363 to +364
if (result._tag === "Failure") {
if (!isAtomCommandInterrupted(result)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reset the restoring phase after an interrupted Undo

When the restore command is interrupted—for example, because a remote WebSocket disconnects before the server executes it—this branch leaves the entry in restoring. After reconnection, a status that still contains the retired root cannot change that phase, so every Undo control remains disabled as “Restoring…” until a page reload. Reset the phase to trashed on interruption; if the server did complete the restore, the next live status will still remove the entry.

AGENTS.md reference: AGENTS.md:L74-L74

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid: an interrupted restore now puts the entry back to trashed (Undo enabled again); if the server did restore it, the next status showing it live still drops the entry. Fixed in a52f45b.

Comment thread apps/web/src/state/trellisTrashed.ts Outdated
Comment on lines +64 to +66
if (retired.has(trimTrailingSlashes(project.workspaceRoot))) {
if (project.phase === "trashing") store.setPhase(key, "trashed");
} else if (project.phase !== "trashing") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reconcile a fast external restore from the trashing phase

A live status never removes an entry while it is still trashing. If another client or the CLI restores the item before this client observes any status containing its retired root—such as after the post-trash status refresh times out—all subsequent statuses show the root live, but the entry remains permanently labeled “In trash” until reload. The reconciliation needs a way to distinguish a pre-trash stale status from a later authoritative live status so multi-device restores can settle.

AGENTS.md reference: AGENTS.md:L74-L74

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid: entries now remember the status cached when they were added (from before the trash) and ignore only that one; any later status settles them, so a restore elsewhere drops the entry even if no status ever showed it trashed. Test added in trellisTrashed.test.ts. Fixed in a52f45b.

…sh state against the pre-trash status

Only the sidebars keep a just-trashed project listed. An entry ignores the
status cached when it was added and is settled by any later one, so a
restore elsewhere drops it even before a status showed it trashed; an
interrupted Undo gives the Undo back.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a52f45b7b2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


export default function LegacySidebar() {
const projects = useSidebarProjects();
const projects = useSidebarProjects({ keepTrashedHere: true });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Disable new-thread actions on retained trash rows

When a project is retained here for Undo, the sidebar still treats it as an ordinary actionable project: SidebarProjectItem's pencil button calls handleNewThread directly with the trashed member, opening a draft whose workspace has been removed, while the default Sidebar also uses its retained projects array for newThreadDisabled, so its sole-project button remains enabled but silently has no valid target. Fresh evidence beyond the earlier target-filtering finding is that both sidebar callers explicitly opt into the retained list even though useHandleNewThread() now uses the filtered list; keep these rows display-only and suppress their creation affordances.

AGENTS.md reference: AGENTS.md:L67-L70

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid: the legacy project header no longer renders its new-thread button while the project is in this client's trash list (Undo only), and the new sidebar's new-thread button is disabled when every listed project is just-trashed; its picker and direct create already use the filtered target list. Fixed in a3c7e1c.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a3c7e1c78b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +334 to +336
...(restore === undefined
? {}
: {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve confirmation when the server cannot provide Undo

When app.t3.codes or a newer desktop client connects to a server predating the new restore field, the optional contract accepts the old { trashed, name } response, but both removal entry points have already skipped confirmation and this branch silently omits Undo. The user therefore gets the new destructive, no-confirm flow without its promised immediate reversal; gate the no-confirm behavior on server support or retain a confirmation for version-skewed remote environments.

AGENTS.md reference: AGENTS.md:L19-L21

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not changing this one. The fork's Trellis T3 runs as one dev_v2 install whose server and web come from the same build (t3code-install pins a version per instance), so a client without Undo support against this server does not occur in our deployment; app.t3.codes and the store desktop app do not speak dev_v2's orchestration protocol 2 at all. Even with an older server the trash stays reversible: the toast points to Settings → Trellis, where Restore brings the item and its conversations back.

Comment on lines +2079 to +2082
const trashedHere = useTrellisTrashedStore((state) =>
project.memberProjects.some(
(member) => trellisTrashedKey(member.environmentId, member.id) in state.projects,
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep new-thread actions for live members of a grouped project

When a logical project groups checkouts from multiple environments and only one member was just trashed, this some(...) marks the entire group as trashed, so the legacy sidebar removes its new-thread button even though another member remains live and the existing environment chooser could target it. Treat the group as unavailable only when every member is trashed, and exclude trashed members from the chooser.

AGENTS.md reference: AGENTS.md:L74-L74

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid: the legacy row now hides its new-thread button only when every member was just trashed. Fixed in b2c40a5.

Comment on lines +474 to 476
// A trashed project stays on this page, with its Undo.
if (isWholeGroup && !hasOtherMembers && deleted.length > 0) {
void navigate({ to: "/", replace: true });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stay on settings when any grouped member was trashed

For a whole logical group containing both a Trellis-managed member and an ordinary member, trashed and deleted are both nonempty; after successfully trashing the first and deleting the second, this condition navigates home merely because deleted.length > 0. That regresses the promised in-place flow and removes the settings-page Undo even though the retained trashed member still owns the route; navigate only when no member was successfully trashed.

AGENTS.md reference: AGENTS.md:L74-L74

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid: settings now navigate home only when nothing was trashed (counting successful trashes, not attempts), so a mixed group keeps its page and Undo. Fixed in b2c40a5.

…tay on settings when anything was trashed

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Andrey170170
Andrey170170 merged commit eb1e712 into dev_v2 Oct 2, 2026
8 of 17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant