Skip to content

fix(widgets): hold a chosen folder's mark to the folder the person chose - #641

Merged
mrgoonie merged 2 commits into
mainfrom
fix/628-chosen-folder-identity
Oct 8, 2026
Merged

mrgoonie merged 2 commits into
mainfrom
fix/628-chosen-folder-identity

Conversation

@mrgoonie

@mrgoonie mrgoonie commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #628. Refs #538, #612, #610, #546.

What was wrong

A folder the person chose for widget development (#538, #612) was matched by path alone. After the chosen folder was moved away, its mark was listed as "not found now". But if any real folder was later made at the same path, the mark counted again, even though it was not the folder the person chose (finding N4 in the #612 review).

What changes

This takes option 1 from the issue: each mark is held to the folder's file identity.

  • When the person's start marks a folder (chosenByPerson), it also stores that folder's device and file id as chosenFolderId: { dev, ino }, in decimal strings, in the node's widget dev session store.
    • The id is read with lstat right after checkRoot resolved the root.
    • If no folder of its own is there by then (for example, a link was swapped in), nothing is kept.
  • A mark is found only when both hold:
  • A folder made in place of the chosen one stays "not found now", grants nothing and can be forgotten. The chosen folder moved back counts again.
  • The live re-arm path, starting again on an already live session, also records the id. Forgetting removes it together with the mark.
  • The "not found now" card note now says that only this same folder coming back restores access, not another folder made there. It is updated in EN and VI.

Marks stored before this change

Older stores have marks with no chosenFolderId. Such a mark takes the id of the folder found at its path the first time one is found there. That happens on the first chosen(), marked(), card or Clark start check after the upgrade. From then on it is held to that folder.

If the folder is missing at that first look, no id is taken. The mark stays "not found now" until a folder is there. There is no record of which folder was chosen before this change, so that first folder is the one adopted.

Store schema

The session store is the JSON file widget-dev/sessions.json, not a SQL table, so no migration is involved. The new field is optional and the store stays at version: 1, so existing files parse unchanged. No SQL migration was added or edited.

Caveat: an older build reading a store written by this one rejects the unknown field, because the schema is strict. It sets the file aside, as it does for any unreadable store. Any new optional field on this strict schema has the same downgrade behaviour.

Residuals (not widened by this PR)

  • On Linux and macOS, a folder that is deleted (not moved) frees its inode. A folder made at the path straight after may be given the same inode, and then it would read as the same folder.
    • The engine avoids this by holding a watched folder open. A stored mark cannot hold its folder open.
    • Moving a folder away keeps its inode in use, so the moved-away case in the issue is closed on every OS. On Windows, NTFS gives a new folder a new file id.
    • Birth time was considered as an extra key. It was left out because libuv falls back to ctime where statx is missing, and that would break the mark on any edit.
  • A chosen folder that a build tool deletes and makes again (rm -rf dir && build) loses its mark, and the person chooses it again. A running session there keeps running: the engine still follows the folder (fix(widgets): detect a replaced dev folder and ignore transient stat errors #610). Only a new start by Clark needs the mark.

Overlap

Tests

New in apps/runtime/test/widget-dev-sessions.spec.ts, using real folders and Windows junctions and run on Windows 11. The temp folder is realpath'd, which covers macOS /private/var.

  • "holds a chosen folder's mark to that folder: another folder made at its path does not count, the chosen one moved back does". It checks:
    • after the move and a new folder at the path: chosen() is [], marked() is found: false, and the card shows "not found now";
    • Clark's start at the path and inside it is refused;
    • the original moved back: found: true, and Clark's start runs.
  • "gives a mark stored without a folder id the id of the folder first found at its path, and holds it to that folder" covers the legacy path:
    • a legacy mark whose folder is missing takes no id;
    • when the folder is found, it takes the same id the start recorded;
    • a later replacement folder does not count.

Both fail on main source: expected [ Array(1) ] to deeply equal [] and expected undefined to be defined.

Verification (this tree)

  • Focused vitest, widget-dev-sessions, slash-commands, open-interfaces and develop-folder-card: 123/123 passed.
  • pnpm typecheck passed.
  • pnpm invariants: 15/15 passed. docs/manifest.json was refreshed for the two edited docs.
  • eslint on the changed files: clean.
  • pnpm verify: 575 files passed (1 skipped), 7856 tests passed.

Docs

A chosen folder was matched by path alone, so after it was moved away any
real folder later made at that path counted as the person's choice again.

The person's start now stores the chosen folder's device and file id
(chosenFolderId) beside the mark, read right after the root is resolved. A
mark counts only while its path resolves to itself and the folder there has
that id, so a folder made in its place stays "not found now" and can be
forgotten, while the chosen folder moved back counts again.

A mark stored before ids were kept has none. It takes the id of the folder
found at its path the first time one is, and is held to that folder from
then on. The session store stays at version 1: the field is optional.

Refs #628
@mrgoonie

mrgoonie commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

Review attestation: ready to merge at 5ba860da4f1319c0285063194d2fc36149c35af9, reviewed by agent:code-reviewer.

A push to this PR makes this attestation stale; the new head needs its own review.

@mrgoonie

mrgoonie commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

Review attestation: ready to merge at e06729ccbf66f57a5e18b03598141190ba5d77d1, reviewed by agent:code-reviewer.

A push to this PR makes this attestation stale; the new head needs its own review.

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.

A chosen folder's mark counts again for a different folder at the same path

1 participant