Skip to content

feat(core): archive projects whose directories are gone - #54354

Merged
thdxr merged 3 commits into
devfrom
project-archive
Oct 11, 2026
Merged

thdxr merged 3 commits into
devfrom
project-archive

Conversation

@thdxr

@thdxr thdxr commented Oct 10, 2026 •

Copy link
Copy Markdown
Member

Issue for this PR

Closes #

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Keeps the project list from growing forever by archiving projects whose folders no longer exist.

  • Adds a nullable project.time_archived column (migration included), exposed as Project.Info.time.archived.
  • Project.sweep runs at server startup and then hourly (project-archive-sweep node). For each unarchived, non-global project:
    • if the canonical directory exists, nothing changes;
    • if it is gone but another recorded main checkout (a clone) exists, the canonical moves there, matching what resolve already does lazily;
    • if only linked worktrees survive, the project stays active;
    • if no known directory exists, it is archived.
      Filesystem errors count as "exists", so a transient failure never archives anything.
  • Project.resolve clears time_archived, so opening any directory of the project brings it back.
  • GET /api/project hides archived projects by default; ?archived=true includes them.
  • No new events: archive and unarchive reuse the existing ephemeral project.updated, sent only when the value actually changes. Archiving leaves time_updated alone, like activate.
  • The Solid client store's project.list() filters archived projects so they disappear live when project.updated arrives.

There is intentionally no manual archive action.

How did you verify your code works?

  • New tests in packages/core/test/project.test.ts: list filtering, archive on sweep plus unarchive on resolve (including the events sent and preserved time_updated), moving the canonical directory to a surviving clone, and never archiving the global project.
  • bun run check passes; core project/location/layer-node/projector/database tests, the server suite, and the TUI data tests pass; bun run migration --check is clean.
  • Regenerated the client, protocol OpenAPI, and www OpenAPI.

Screenshots / recordings

N/A

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

— from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊


Added by the opencode bot (AI-generated).

Fixes #54320

Related: #49442, #39903, #51197

Public API: GET /api/project hides archived projects by default and adds the optional archived query parameter; adds optional Project.Info.time.archived (additive; callers that want every project need archived=true)

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Works as described, and I found nothing blocking. I checked it against a real server: after deleting a project's directory and restarting, GET /api/project hides the project, ?archived=true returns it with time.archived set, and opening the directory again brings it back with time.updated unchanged. The TUI open dialog (Ctrl+O) no longer lists the deleted project (screenshots below). Core project/location tests pass, and core, server and client type-check.

Public API: all changes are additive, with one change to default behaviour.

  • GET /api/project gets an optional archived query parameter ("true"/"false"). It also now excludes archived projects by default, so any outside caller that expected every known project has to pass archived=true.
  • Project.Info.time.archived is a new optional field.
  • The client's project.list(input?) accepts { archived?: boolean }.
  • project.updated is reused with no new event types.
  • There is a new nullable project.time_archived column, with a migration.

One gap worth closing: packages/app (web and desktop) gets its project list from the now-filtered API, but its project.updated handler (applyProjectUpdate in packages/app/src/runtime/server/sync.tsx) only patches the projects it already has. A project archived by the hourly sweep therefore stays in the app's lists until they are fetched again, and a project brought back by opening it doesn't reappear until then either. The Solid client store change only covers the TUI side.

Fixes #54320
Related: #49442, #39903, #51197

Before

After

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cli: projects whose directory was deleted are never removed from the recent projects list

1 participant