Refuse folder drops at hover, skip them in mixes

04's settled ruling: the attachment model is flat top-level files, so a
drag containing only folders never engages — no highlight, no proposal,
the standard incompatible-payload cursor — and a mixed drag proposes for
its files only, importing them at the drop while a loss row names the
skipped folders. Hover reads the providers' registered types (anything
conforming to public.directory refuses, packages included); commit
re-partitions authoritatively from the filesystem, so a synthetic
payload that hides its type still can't land a folder. The create path
now only ever fires with at least one importable file — the
mint-fail-remove dance is gone from the folder case and stays reserved
for genuine mid-batch failures.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-07-28 07:29:43 -04:00
parent 7be4eec9fd
commit 1487b391ad
4 changed files with 379 additions and 33 deletions
+9 -3
View File
@@ -1669,6 +1669,12 @@ public final class BoardStore {
// ordinary store writes, with the drop commits' own rules above (one `performWrite` bracket per
// gesture; a vanished or tombstoned destination is a silent no-op, the reload being the
// authority; failures are the banner's).
//
// **Folders never arrive here from a drop.** "Folders are refused at hover" (04-interactions.md):
// the gesture refuses a folders-only drag outright and `FinderDrop.land` partitions a mixed one
// before it calls either of these, naming the skipped folders in a loss row. Both functions stay
// honest about a directory anyway `BoardWriter.importAttachments` refuses one by design since
// nothing about their contract says a drop is the only caller.
/// Copies `urls` into `cardID`'s `attachments/` the drop-on-a-card half.
///
@@ -1711,9 +1717,9 @@ public final class BoardStore {
/// (01-storage-format.md § Frontmatter).
///
/// **Partial failure is honest, and leaves no half-made card.** The batch stops at the first file
/// that cannot be imported a folder rather than a file, an unreadable source which banners
/// naming it; the cards already made keep their files, matching `importAttachments`' own
/// "everything already imported stays landed". The card whose import failed is removed again
/// that cannot be imported an unreadable source, a vanished one, a disk with no room left
/// which banners naming it; the cards already made keep their files, matching `importAttachments`'
/// own "everything already imported stays landed". The card whose import failed is removed again
/// before the throw: it was minted moments earlier in this same bracket and holds nothing but
/// what this call put there, and "creating-then-abandoning never leaves an empty card behind"
/// (04-interactions.md Grammar) is the rule it would otherwise break.