Skip to content

Add realm config templates#4973

Draft
backspace wants to merge 42 commits into
mainfrom
edit-realm-config-cs-10056
Draft

Add realm config templates#4973
backspace wants to merge 42 commits into
mainfrom
edit-realm-config-cs-10056

Conversation

@backspace

@backspace backspace commented May 26, 2026

Copy link
Copy Markdown
Contributor

This adds templates and other changes for managing host routing.

The card chooser won’t let you choose from another realm:

CleanShot 2026-06-08 at 15 09 09@2x

There‘s a warning when there’s a duplicate path or invalid segment:

CleanShot 2026-06-08 at 15 08 08@2x

The interact template for the realm config shows the routes and what cards they route to:

CleanShot 2026-06-08 at 15 10 36@2x

@github-actions

github-actions Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

Preview deployments

Host Test Results

    1 files  ±0      1 suites  ±0   1h 35m 2s ⏱️ - 1m 27s
2 970 tests ±0  2 954 ✅ ±0  15 💤 ±0  0 ❌ ±0  1 🔥 ±0 
2 989 runs  ±0  2 972 ✅ ±0  15 💤 ±0  1 ❌ ±0  1 🔥 ±0 

Results for commit 1580773. ± Comparison against earlier commit ac9bfa4.

For more details on these errors, see this check.

Realm Server Test Results

    1 files  ±0      1 suites  ±0   10m 26s ⏱️ -2s
1 587 tests ±0  1 587 ✅ ±0  0 💤 ±0  0 ❌ ±0 
1 678 runs  ±0  1 678 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 1580773. ± Comparison against earlier commit ac9bfa4.

backspace and others added 22 commits May 27, 2026 09:28
Renders the realm name, icon, and host routing rules so the config
card is legible in catalog and stack views, not just the default
field-by-field form.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…10056

# Conflicts:
#	packages/base/card-api.gts
The Realm Settings tile-menu action opened the realm config card behind
the still-open chooser overlay, so it looked like nothing happened.
Dismiss the chooser after opening, and cover it with an acceptance test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Opens the RealmConfig editor, triggers the routing rule instance
chooser, and asserts the catalog realm picker is locked so only
same-realm cards can be linked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a sectioned edit template on RealmConfig — Appearance, Host
Routing Rules, Advanced — so the realm config form reads as a real
settings screen rather than a stack of raw fields. Rework the
routing rule editor into a compact `path → instance` row that
mirrors the atom's shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Workspace A's setup already gets its RealmConfig card via the
realmConfigCardJSON helper a few lines above, so the second
inline realm.json entry was a duplicate-key error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Non-blocking advisory: surface duplicate paths above the
hostRoutingRules list so a realm owner notices before
saving. Uniqueness validation is intentionally out of scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Show a per-rule advisory under the path input when the path is
non-empty but doesn't start with / or contains characters outside
the allowed set for a static path (letters, numbers, /, -, _, .,
or ~).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The advisory regex rejected %XX escapes, which are valid per
RFC 3986. Accept a `%` followed by exactly two hex digits in
addition to the unreserved character set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the two pure helpers out of the editor template into
runtime-common: validateRoutingPath checks a single rule's path
format, findDuplicateRoutingPaths scans the full list for repeats.
The RealmConfig and RoutingRuleField editors now delegate to them.

Cover both helpers in runtime-common/tests as a SharedTests module,
wired into the realm-server qunit suite via the standard wrapper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add two host integration tests that prove the editor template wires
the routing rule advisory validators through to rendered warnings:
one for the per-rule malformed-path message, one for the aggregate
duplicate-path banner. Existing realm-lock test refactored to share
the same setup helper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror the default card-edit template (cardInfo header, FieldContainer-
wrapped non-routing fields, notes footer) so iconURL, backgroundURL,
and includePrerenderedDefaultRealmIndex render with their standard
chrome. Only the hostRoutingRules FieldContainer carries custom UI —
the duplicate-path advisory.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The A/B comparison against the default template is done. Wire the
custom edit back in and drop the temporary `export` on the class.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A field declaration can now supply its own edit component via the
`edit:` option, overriding the FieldDef's `static edit` for that
specific usage. Two integration points:

- Atomic fields (linksTo / contains): the override replaces the
  resolved edit component in field-component's lookup.
- containsMany fields: the override receives `@model` (the containing
  card), `@values` (the array of items), and `@defaultEditor` (a
  pre-bound ContainsManyEditor) so it can wrap — not replace — the
  standard iteration / add / remove / sortable UI.

Rewrite RealmConfig to use the wrap-style override for hostRoutingRules:
the duplicate-path advisory now lives in a small standalone editor
attached to the field, and every other field renders via the default
card edit template. Drops the previous RealmConfigEdit that mirrored
the default layout to splice the banner in.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The override branch in field-component sees the same field descriptor
for both the outer collection render and each iterated item inside
containsMany. Without a gate it would replace each item's edit
template with the collection-level override component, blanking
every row. Restrict it to atomic field types; collection fields
go through their own wrap-style handler.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The path input shows a non-editable "/" accessory before the
editable text. The stored path always carries a leading "/", so
users can't accidentally drop it by backspacing through the field,
and the missing-slash advisory is unreachable from the editor
(it stays correct for hand-edited realm.json files).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the BoxelInputGroup horizontal padding on the path cell so
the leading "/" sits visually next to the editable text rather
than floating with a wide margin.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The earlier --boxel-input-group-padding-x override on .path-cell was
shadowed by BoxelInputGroup's own scoped CSS resetting the variable
on its inner container. Target the consumer classes directly via
:deep instead so the right-of-accessory and left-of-input padding
collapse as intended.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire the same branch into LinksToComponent (atomic, replaces
LinksToEditor) and LinksToMany's wrap-style path (with
@defaultEditor matching containsMany). Add `readonly edit?` to each
Field class so reading `field.edit` from those templates typechecks.

Also cover the realm config editor's wrap contract and path
normalizer behavior with integration tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
backspace added 4 commits June 5, 2026 13:53
…10056

# Conflicts:
#	packages/base/card-api.gts
#	packages/base/contains-many-component.gts
#	packages/base/field-component.gts
#	packages/base/links-to-many-component.gts
The Named-args type for `<@fields.foo />` invocations needs to
accept `lockConsumingRealm` so that
`<@fields.instance @lockConsumingRealm={{true}} />` typechecks.
The addition was originally made when the lock-realm option
landed but got dropped during the last merge from main when the
field-component.gts conflict was resolved in favor of main's
version (which carries the per-usage edit override but not the
lock-realm plumbing).
The path input renders an empty field next to a fixed "/"
accessory, so a rule with no stored path is visually identical
to one with an explicit "/". The duplicate-path warning compared
the underlying data, treating those visually-equal rules as
distinct because one held undefined and the other "/", so two
"/" rules — one fresh, one explicit — never tripped the warning
even though the user clearly saw the conflict. Normalizing
unset paths to "/" on mount aligns the data with what's shown
and removes the only "draft path" state, which has no runtime
meaning for the realm-server router.
backspace added 5 commits June 5, 2026 17:28
Setting model.path inside the constructor triggered autosave
synchronously, which wrote autoSaveState.isSaving inside the
same render computation that had already read it via the
CardHeader's saving indicator — Glimmer rejects read-then-write
on a tracked cell within one computation. Wrap the assignment in
queueMicrotask so it runs once the current render has settled,
guarded against firing after the component is torn down.
A disabled Picker still rendered each selected item with the X
remove affordance, but clicking it did nothing — the underlying
power-select rejected the action because the select is disabled.
That left a dead UI element on the realm picker when the card
chooser is locked to the consuming realm. Honour `@select.disabled`
in the selected-item alongside the existing select-all carve-out,
mirroring how the trigger already hides its caret when disabled.
The isolated template read `rule.instance.title`, but CardDef
exposes the display name as `cardTitle` (computed from
`cardInfo.name`). The typo meant each rule rendered the arrow
followed by nothing, even when the rule was wired to a real card.
Iterating @fields.hostRoutingRules instead of @model.hostRoutingRules
hands each item to RoutingRuleAtom, whose template already wires up
<@fields.instance @Format='atom' />. That gives readers a clickable
pill for the linked card instead of a plain text title — same
behavior they get everywhere else a CardDef is rendered as an atom.
Drop the per-usage `edit:` option on hostRoutingRules and the
RealmConfigRoutingRulesEditor wrap component that consumed it.
The per-usage field-edit primitive is going to be removed while
its API is reworked, so this branch can't rely on it.

The replacement is a `static edit` on RealmConfig that wraps
DefaultCardDefTemplate so the standard CardInfo header,
generic field section, and notes footer keep rendering as
before; the only addition is the cross-rule duplicate-path
advisory banner above the form. Per-rule routing path UI
(slash accessory, validation, unset-path normalization) stays
unchanged because RoutingRuleField still owns its own
`static edit = RoutingRuleEdit` and runs through the default
ContainsManyEditor now.
backspace added 9 commits June 8, 2026 14:42
Previously the warning sat above the entire form because RealmConfigEdit
wrapped DefaultCardDefTemplate as a single block. Replace the wrap with
an inlined copy of the standard CardDef edit scaffold — CardInfo header,
displayFields iteration, notes footer — so the warning can render
directly above the hostRoutingRules row inside the form. Per-field
rendering is unchanged: each field still uses its own default Component;
only the surrounding chrome is replicated, kept in sync with
default-templates/isolated-and-edit.gts.
…10056

# Conflicts:
#	packages/base/card-api.gts
* `super(owner, args)` in RoutingRuleEdit was passing `owner: unknown`
  where the GlimmerComponent constructor wants `Owner` — type the
  parameter as `Owner` and import it.
* `CardInfoTemplates.edit` declares `@model: CardDef`, but
  `Component<typeof RealmConfig>` exposes `@model` as
  `PartialFields<RealmConfig>` (every field, including `id`, possibly
  undefined). Add a `baseModel` getter that casts to the looser shape
  the template actually exercises.
The chooser-locked test waited on `[data-test-realm="Local
Workspace"]`, but that selector reads the user-visible name from
`realm.info()` — an async fetch. search-result-section.gts spells
out the race: until `info()` resolves the section renders with the
"Unknown Workspace" placeholder, so the name-based selector misses
and `waitFor` times out. Switch the wait and the cross-realm
assertion to `data-test-realm-url`, which is URL-keyed and stable.
Temporary: emit a data-test attribute on LinksToEditor showing what
`@lockConsumingRealm` arg actually arrives, plus an early test
assertion checking it. Tells us whether the value flows from
RoutingRuleEdit's <@fields.instance @lockConsumingRealm={{true}} />
through to LinksToEditor, which will pinpoint where the chain breaks
in CI. Will revert once we have an answer.
Use hasAttribute instead of selector matching so when the assertion
fails the message reports the actual attribute value, telling us
whether @filter.locked arrived as undefined, false, or some other
falsy shape.
Operator-mode's SearchSheet renders its own SearchPanel + RealmPicker
alongside the card-catalog modal's, and the SearchSheet's panel never
receives `@lockSelectedRealms`. The bare `[data-test-realm-picker]`
selector matched the SearchSheet's picker first, so the locked
assertion always read its undefined `@filter.locked` even when the
modal's picker was rendered correctly with locked=true. Scope every
realm-related selector in the test to
`[data-test-card-catalog-modal]` so it can only resolve to the
modal's picker / realm sections.

Also drops the [lock-diag] console.logs and data-test-lockarg-value
attribute that surfaced the picker mix-up.
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.

1 participant