Skip to content

CardChooserModal: mixed cards+files mode with {id, kind} return#5546

Merged
lukemelia merged 4 commits into
cs-12204-file-id-unificationfrom
cs-12205-chooser-mixed-mode
Jul 20, 2026
Merged

CardChooserModal: mixed cards+files mode with {id, kind} return#5546
lukemelia merged 4 commits into
cs-12204-file-id-unificationfrom
cs-12205-chooser-mixed-mode

Conversation

@lukemelia

Copy link
Copy Markdown
Contributor

Closes CS-12205. Phase 2 of the mixed cards+files chooser (parent CS-11809). Stacked on #5531 (CS-12204, file id unification) — review/merge that first.

What

Extend CardChooserModal to run in a mixed cards + files mode via chooseCard(query, { includeFiles: true }), scoped by a base filter, returning each pick tagged with its kind. Most of the data layer already exists from #5428 (mixed search is the default; file rows already render as result tiles).

  • Parameterize cardsOnly: the two hardcoded @cardsOnly={{true}} (SearchPanel, Content) become {{not state.includeFiles}}, so mixed mode surfaces file rows and the type picker offers file types.
  • Widen the query type: CardChooserQuery.filter now allows AnyFilter so a mixed any base filter type-checks.
  • {id, kind}[] return contract: expose kind ('card' | 'file') on RenderableSearchEntryLike / the SearchEntry view-model, derived by reusing the existing file-detection heuristic in search-entries.ts (no new classification path). Thread a kind-tagged selection payload (SearchSelection / SelectedSearchItem) through the result tile → modal so pickCards returns ChosenItem[]. Cards-only callers are unaffected — the chooseCard wrapper projects mixed results back to bare id string(s) unless includeFiles is set.
  • chooseCardTitle (ancillary): reviewed — an any filter falls through to the neutral default title, no crash. Left as-is per the ticket's non-blocking note; Phase 3 supplies the real title.

Tests

  • search-entries-test: a card row is tagged kind: 'card'; both the item-only and html-only file rows are tagged kind: 'file'.
  • card-search-adorn-test (result tile): clicking a card row emits { id, kind: 'card' }; a file row emits { id, kind: 'file' } — the tile is what stamps the kind onto the payload.
  • card-chooser-mixed-mode-test (new): a real-index modal driven by the global chooseCard({ includeFiles: true, multiSelect: true }) with the canonical { type: baseRef } mixed filter surfaces both a card instance and a markdown file, and resolves kind-correct { id, kind } picks.
  • Updated card-search-selection-menu-test for the SelectedSearchItem selection type.

lint:types (host + runtime-common), eslint, and ember-template-lint are green.

Note

Depends on #5531. The kind on file rows relies on the file id unification only for downstream exclusion (Phase 3); this PR's kind derivation is independent. No reindex required for this PR on its own.

🤖 Generated with Claude Code

Extend the card chooser to run in a mixed cards + files mode
(`chooseCard(query, { includeFiles: true })`), scoped by a base filter and
returning each pick tagged with its kind.

- Parameterize `cardsOnly`: the two hardcoded `@cardsOnly={{true}}` become
  `{{not state.includeFiles}}`, so mixed mode surfaces file rows and offers
  file types in the type picker.
- Widen `CardChooserQuery.filter` to allow `AnyFilter` so a mixed `any` base
  filter type-checks.
- Expose `kind` ('card' | 'file') on `RenderableSearchEntryLike` / the
  `SearchEntry` view-model, derived by reusing the existing file-detection
  heuristic. Thread a kind-tagged selection payload (`SearchSelection` /
  `SelectedSearchItem`) through the result tile and modal so `pickCards`
  returns `ChosenItem[]` (`{ id, kind }`). Cards-only callers still get bare
  id strings.

Tests: resource-level kind derivation for card/file rows; the tile emits the
correct kind-tagged payload; and a real-index modal test that a mixed chooser
surfaces both a card and a file and resolves kind-correct picks.

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

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Preview deployments

Host Test Results

    1 files  ±0      1 suites  ±0   3h 9m 0s ⏱️ + 6m 33s
3 549 tests +1  3 534 ✅ +2  15 💤 ±0  0 ❌ ±0 
3 568 runs  +1  3 553 ✅ +3  15 💤 ±0  0 ❌  - 1 

Results for commit cbe8148. ± Comparison against earlier commit 2c2ea62.

Realm Server Test Results

    1 files  ±0      1 suites  ±0   14m 15s ⏱️ - 1m 53s
1 878 tests ±0  1 878 ✅ ±0  0 💤 ±0  0 ❌ ±0 
1 957 runs  ±0  1 957 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit cbe8148. ± Comparison against earlier commit 2c2ea62.

Comment thread packages/host/app/components/card-chooser/modal.gts Outdated
Comment thread packages/host/app/components/card-chooser/modal.gts Outdated
Comment thread packages/host/app/components/card-chooser/modal.gts Outdated
The `.json` extension is a card-id convention, so stripping it from a
`kind: 'file'` selection rewrote a `.json`-suffixed file id (e.g. a
standalone `settings/config.json`) into a card-instance id shape while
still tagging it `file` — the caller then resolved the wrong resource.

- `pickCard` and the "Select All" dedup in `sheet-results` now strip
  `.json` only for card kinds; file ids pass through unchanged.
- `selectionEquals` compares `kind` too, so a card and its backing
  `.json` file no longer toggle each other in a multi-select chooser.
- Consolidate the card-id normalization onto the shared
  `removeCardJsonExtension` (drops the local `normalizeCardUrl` and the
  inline `.replace(/\.json$/, '')` copies in the selection path).

Test: Select All in the mixed chooser returns a standalone `.json`
file with its `.json` id intact and kind=file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lukemelia
lukemelia marked this pull request as ready for review July 20, 2026 17:24
@lukemelia
lukemelia requested review from a team and FadhlanR July 20, 2026 17:39
lukemelia and others added 2 commits July 20, 2026 14:09
`chooseCard(query)` accepted `query.realms` but the modal ignored it and
always searched every available realm. Harmless for a cards-only chooser
(few instances per realm), but in mixed mode (`includeFiles`) it renders
a tile for every matching file in every realm — so a scoped caller drowns
under unrelated realms' files and its intended result may never render.

Pin the search to `query.realms` when provided: seed the panel's selected
realms from it and lock the realm filter. Callers that omit `realms` keep
the prior all-realms behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`scopeFilters` skipped the `excludeCardInstanceFileRows` dedup whenever a
type was selected in the picker (`Boolean(typeFilter)`). But the chooser
seeds the picker from its base filter, so a root base filter (`BaseDef`)
becomes a selected *root* type — which still spans both kinds. Skipping
the dedup there leaked a card's dual-indexed `.json` file row into the
mixed chooser as a duplicate of the card's instance row (colliding on the
`.json`-stripped id).

Gate the skip on an actually-narrowing (non-root) selected type by reusing
`hasNarrowingPositiveTypeRef` on the type filter instead of a bare presence
check. Narrowing picks (a specific card or file type) still skip the dedup
as before.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lukemelia
lukemelia merged commit 5bfad1a into cs-12204-file-id-unification Jul 20, 2026
102 of 106 checks passed
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.

2 participants