Add host mode routing from realm.json#4709
Conversation
CS-10054: assert getHostRoutingMap returns [{ path, id }] pairs read
from the indexed RealmConfig card. The test fails today (method does
not exist) and pins the desired shape for the implementation pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Preview deploymentsHost Test Results 1 files ±0 1 suites ±0 1h 47m 36s ⏱️ + 13m 38s Results for commit 0d7b009. ± Comparison against earlier commit 60aa1ba. Realm Server Test Results 1 files ±0 1 suites ±0 8m 18s ⏱️ -1s Results for commit 0d7b009. ± Comparison against earlier commit 60aa1ba. |
…s-10054 # Conflicts: # packages/realm-server/tests/index.ts
…s-10054 # Conflicts: # packages/realm-server/server.ts # packages/realm-server/tests/index.ts
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8e42fa0b0c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
This PR adds “host mode” routing based on hostRoutingRules stored in a realm’s realm.json (RealmConfig card), allowing published realms to serve custom bare paths (e.g. /whitepaper) that render a target card both server-side (SSR head/isolated/scoped CSS) and client-side (SPA hydration/navigation).
Changes:
- Add
Realm.getHostRoutingMap()to read routing rules from the indexed RealmConfig card and return a{path,id}map. - Update realm-server
serve-indexto (a) rewrite SSR fetch target based on a matched routing rule and (b) injecthostRoutingMapinto the host environment meta tag per request. - Add unit + e2e coverage for routing behavior and adjust host app to resolve routed paths before building the default card URL.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/runtime-common/realm.ts | Adds getHostRoutingMap() to read and validate routing rules from the indexed RealmConfig card. |
| packages/realm-server/handlers/serve-index.ts | Applies routing rules for SSR rendering and injects hostRoutingMap into the environment meta tag; tweaks meta-tag rewrite regex. |
| packages/host/config/environment.js | Adds default hostRoutingMap to the host config surface for realm-server injection. |
| packages/host/app/services/host-mode-service.ts | Exposes hostRoutingMap from config and adds resolveRoutedPath() helper. |
| packages/host/app/routes/index.gts | Uses routing resolution to pick the routed target card ID before default URL construction in host mode. |
| packages/base/realm-config.gts | Changes routing rule instance field to a flat string (instead of a JSON:API relationship) so it indexes as attributes. |
| packages/realm-server/tests/realm-routing-test.ts | Adds a QUnit test covering Realm.getHostRoutingMap() behavior. |
| packages/realm-server/tests/index.ts | Registers the new routing test in the realm-server test suite. |
| packages/matrix/tests/host-mode.spec.ts | Adds an end-to-end test that publishes routing rules and verifies bare-path resolution renders the target card in host mode. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
The project spec restricts routing rules to cards within the same
realm, so the non-URL reference forms accepted by resolveRRI
(`$REALM/...`, `@cardstack/<package>/...`) are over-engineering
here. `getHostRoutingMap` now resolves via plain
`new URL(instance, realmBase)` and returns absolute URLs only,
matching the project doc's flat `{ "/path": "id" }` example shape.
Same-realm guard drops to a plain `id.startsWith(this.url)` since
both sides are URL form; same end behavior, smaller surface.
Unit test trimmed to the two cases that exercise the now-supported
input forms: a relative ref (resolved to URL) and a cross-realm
absolute URL (dropped by the guard).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`new URL(instance, realmBase).href` already normalizes dot-segments, so the obvious `https://host/realm/../private/card` bypass doesn't actually fire here — but plain `startsWith` is still fragile around trailing-slash variance and neighbouring-prefix realms (`/realm-evil/`). `RealmPaths.inRealm` handles both correctly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously the handler called findOrMountRealm twice on the hot HTML path — once inside hasPublicPermissions(cardURL, ...) and once again in the routing-map block. Both can fall back to a DB probe when the in-memory registry is cold. Resolve the realm once at the top and pass it directly into the permissions check. hasPublicPermissions now accepts a Realm | undefined (undefined → false, preserving the prior "no realm = no public read" semantics) instead of doing its own realm lookup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous comment said `path` was "within the realm", but both sides of the comparison are host-relative paths: the server prefixes each rule's path with the realm's mount pathname before injecting, and the route passes Ember's `/*path` catch-all value. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The contains(StringField) experiment got the realm.json file flatter (no relationships split) but lost typed linking, broken-link visibility, and parity with how the rest of the system addresses cards. The flat shape is recoverable in tooling; the lost type information is not. Switching back. getHostRoutingMap now reads searchDoc.hostRoutingRules[i].instance.id — the flattened searchDoc form that linksTo produces. No relative-URL resolution needed at read time; ids arrive as absolute URLs. The RealmPaths.inRealm same-realm guard stays unchanged. Test fixtures (realm-routing-test, host-mode.spec, local routing realm) move the link target from `instance: './foo'` in attributes to `relationships['hostRoutingRules.<i>.instance'].links.self`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
lukemelia
left a comment
There was a problem hiding this comment.
Seems like we may want to cache the host routing map, but looks good for now.
👍🏻 recorded as CS-11206, thanks |
With this branch deployed to staging, when a realm config has
hostRoutingRules:The published realm resolves custom routes: