Realign search, clipboard, and lane-hover code with the second batch
Creation now clears the search by mechanism, not gesture: one seam (noteUserCreation) states 04's rule once, called from the placeholder funnel, paste — cards and lanes, after the staleness guard so a stale paste clears nothing — and Finder file-drop creation; the attach path deliberately doesn't clear, and cross-board arrivals and New Lane stay outside the seam (a transfer isn't creation; a lane can't be born invisible). An open inline rename now survives the filter hiding its card: the model already kept the editor, but the field renders in the card's slot, so renderedCards keeps the renaming card's slot exactly as long as the editor is open — the query stands throughout, and commit or Escape lets the predicate apply in the same pass. Verified conformant and newly pinned: query-emptied lanes keep their slot with a 0 badge, pasteboard staleness (takeover before paste and mid-staging both no-op), out-transition reachability and the strip pre-divide hold by construction with comments citing their rulings. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -298,7 +298,14 @@ struct LaneView: View {
|
||||
/// masonry iterates. That is deliberate rather than incidental: "The count reads the search
|
||||
/// filter like every other surface — during a search it shows the visible count, not the
|
||||
/// total", so when m5's search card narrows `renderedCards` to the filter's survivors the badge
|
||||
/// follows by construction, with no second rule to keep in step.
|
||||
/// follows by construction, with no second rule to keep in step. **A lane the query empties
|
||||
/// shows `0` and keeps its slot** (04-interactions.md § Search, settled: "lanes are never
|
||||
/// filtered out … the search filters cards, and the board's structure is not a search result") —
|
||||
/// which is `BoardView.liveLanes` never consulting the filter at all, made visible here.
|
||||
///
|
||||
/// The rename exemption rides along for the same reason every other rule does: the badge counts
|
||||
/// what the body renders, and while an inline rename is open its card is one of the things the
|
||||
/// body renders (see `renderedCards`).
|
||||
private var countBadge: some View {
|
||||
Text("\(renderedCards.count)")
|
||||
.font(.caption)
|
||||
@@ -708,10 +715,43 @@ struct LaneView: View {
|
||||
/// `countBadge`), the drop zones' resting layout, the marquee registration and the Finder
|
||||
/// file-drop targets all read this list or the registry it populates, so none of them needs a
|
||||
/// rule of its own.
|
||||
///
|
||||
/// **With one exception, and it is the open inline rename** (04-interactions.md § Search,
|
||||
/// settled): "an open inline rename survives the filter hiding its card — the editor is a surface
|
||||
/// the filter doesn't reach; it stays open and focused, commits by UUID wherever the card lives,
|
||||
/// Escape abandons". The editor is drawn *inside* its card's slot (`CardFaceView.isRenaming`), so
|
||||
/// on this board a surface the filter doesn't reach means precisely a slot the filter doesn't
|
||||
/// take away: filtering the card out would unmount the field mid-keystroke and silently drop what
|
||||
/// the user had typed, which is the dirty-buffer courtesy read backwards. The exemption lasts
|
||||
/// exactly as long as the editor — commit or Escape retires it, the predicate applies again in
|
||||
/// the same pass, and a card that no longer matches animates out then (which is also the whole of
|
||||
/// "rename deliberately gets no carve-out": the *query* still stands throughout).
|
||||
///
|
||||
/// It cannot arrive by typing, because focusing the search field is focus loss and commits the
|
||||
/// rename first; the case it serves is a foreign edit that stops the card matching while the user
|
||||
/// is renaming it.
|
||||
private var renderedCards: [Card] {
|
||||
let hidden = drops.session.hiddenMembers(onBoardRooted: store.rootURL)
|
||||
let filter = store.searchFilter
|
||||
return lane.cards.filter { !$0.isDeleted && !hidden.contains($0.id) && filter.matches($0) }
|
||||
Self.rendered(
|
||||
lane.cards,
|
||||
hiddenByDrag: drops.session.hiddenMembers(onBoardRooted: store.rootURL),
|
||||
filter: store.searchFilter,
|
||||
renaming: store.transient.renameEditor?.targetID
|
||||
)
|
||||
}
|
||||
|
||||
/// `renderedCards` as a pure function of its four inputs — see there for every rule it applies.
|
||||
/// Split out only so the rules can be pinned without a view (`SearchFilterTests`); the lane's
|
||||
/// masonry, its count badge and its drop zones all read the property, which reads this.
|
||||
nonisolated static func rendered(
|
||||
_ cards: [Card],
|
||||
hiddenByDrag hidden: Set<ItemID>,
|
||||
filter: SearchFilter,
|
||||
renaming: ItemID?
|
||||
) -> [Card] {
|
||||
cards.filter { card in
|
||||
guard !card.isDeleted, !hidden.contains(card.id) else { return false }
|
||||
return filter.matches(card) || card.id == renaming
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Selection
|
||||
|
||||
Reference in New Issue
Block a user