Add Cards/Files/Definitions metadata to workspace favorites - #5677
Add Cards/Files/Definitions metadata to workspace favorites#5677cmgardella wants to merge 8 commits into
Conversation
…New Workspace tile Favorite tiles now show real Cards/Files/Definitions counts pulled from the realm index instead of card-count/recent-activity stats, and the collaborator avatar stack is removed. The "New Workspace" tile now sits first in Your Workspaces instead of last. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Preview deploymentsHost Test Results 1 files ± 0 1 suites ±0 2h 53m 18s ⏱️ + 2h 47m 10s Results for commit 05e0056. ± Comparison against earlier commit fcf1e08. Realm Server Test Results 1 files ± 0 1 suites ±0 15m 8s ⏱️ + 1m 40s Results for commit 05e0056. ± Comparison against earlier commit fcf1e08. |
Cards/Files/Definitions counts and the realm's created/updated timestamps now come from `Realm#getDetailedRealmInfo`, used by both `/_info` and the realm server's batch `/_federated-info` — the latter is what the host's workspace chooser actually reads. They are deliberately not part of `parseRealmInfo`/`getRealmInfo`, whose result is embedded in every card response's `meta.realmInfo` and hashed into the card+json ETag: values that move on an ordinary realm write would invalidate every card's cached representation in the realm whenever one card changed, and cost extra queries on every card request. Counting fixes: - Count per distinct url rather than per row. A card instance is indexed as both an `instance` row and a `file` row at the same url, so counting rows put every card into the file count too. - Drop the `generation = current_generation` predicate. That column is a last-touched watermark that an incremental index only bumps on the rows it rewrote, so pinning it counted the files touched by the most recent index pass rather than the realm's contents. Deletions are tombstoned via `is_deleted`, matching how the query engine scopes a live search. - Use adapter-portable SQL. `count(*)::int` is Postgres-only and threw on the sqlite adapter the host tests use. Read realm_metadata and realm_registry independently instead of joining them. Keying the metadata read off realm_registry dropped showAsCatalog/publishable for any realm with a metadata row but no registry row. Drop recentActivityCount and collaboratorUsernames: nothing renders them, and the collaborator list exposed every matrix user with realm access to any realm reader — a wider audience than the owner-gated `/_permissions` route. Workspace chooser: - Keep the initial keyboard selection on a workspace. The New Workspace tile now renders first, and the selected tile takes focus, so starting there made the first Enter create a workspace instead of opening one. - Restore the `.is-selected` ring, so keyboard-selected tiles stay visible. - Give the favorite star a tooltip, sharing one getter with its aria-label. - Collapse the three repeated stat blocks into one `tileStats` loop. Tests: rewrite the keyboard-navigation tests for the new tile order, and add coverage for the metadata row, tile ordering, catalog sort, the menu footer, the star tooltip, the date formatters, and the index counts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
/_info runs an aggregate over every index row in the realm, and the realm server's /_catalog-realms fans out to one _info per catalog realm on top of the host's own calls. Uncached, that took per-realm _info from a ~44ms median to ~200ms (max 762ms) in the matrix suite. Cached separately from #cachedRealmInfo rather than folded into it, because that object is hashed into the card+json ETag and these values move on every realm write. Dropped by the same paths that drop #cachedRealmInfo, so the counts still refresh on every index swap. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
/_catalog-realms issues one _info per publicly-readable realm, drops any realm whose response isn't a 200, and caches the resulting list for the life of the server process. Putting per-request index aggregation behind that cold fan-out risked the catalog list for no benefit: the workspace chooser reads its tile metadata from /_federated-info, which still serves the detailed variant. /_info is now byte-identical to main again. The count assertions move to Realm#getDetailedRealmInfo directly, and a new test pins the contract that /_info omits the extras. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Only a favorited workspace tile renders the Cards / Files / Definitions row, but the counts were riding along on /_federated-info, which the host loads for every realm at boot. The counts are the expensive half — an aggregate over every index row in a realm — while the realm timestamps beside them are a single indexed row lookup, so the two are split: - /_federated-info keeps the timestamps. Needed more widely than the counts: createdAt orders the catalog list, and both feed the per-tile options menu. - /_federated-index-counts is new, behind the same multiRealmAuthorization contract, and returns counts only for the realms it is asked about. The host requests counts for favoriteRealmIdentifiers alone, from a modifier on the Favorites list so it runs after render and re-runs when the set changes. RealmService.loadIndexCounts is fire-and-forget and skips realms already loaded or in flight, so the dashboard never waits on it; counts land in a tracked map keyed by realm URL, separate from the realm info because they arrive on their own schedule. The stats row is now rendered unconditionally on favorite tiles with its height reserved, so the numbers fill existing space rather than growing the tile and shifting its centered name. Counts stay memoized per index generation on the realm and are dropped by the same invalidation paths as the realm info, so a re-render costs nothing and a write is still reflected after the swap. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
If the realm fixture build fails, the realms are never assigned and an unguarded teardown throws on unsubscribe before closeServer runs — leaking the bound port so every later test in the process reports EADDRINUSE instead of the original failure. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The realm server dropped its memoized counts on index swap, but the host kept its copy for the session — so a favorited tile showed numbers from whenever the chooser first asked. The only existing re-index refresh is deliberately narrow (RealmConfig-card invalidations, for renames), and widening that would clobber client-managed publish state. Counts have no such hazard, so any completed index now marks them stale. Marked, not refetched: the displayed numbers stay on screen so a write doesn't blank the stats row, and a realm nobody is looking at costs nothing. The workspace chooser's loader takes a revision argument that changes on invalidation, which is what re-triggers the fetch on its next render — keyed only on which realms are favorited, it would never re-run when the answer for those realms changed. Also correct the endpoint test's expected card count: a realm's own RealmConfig card at realm.json is an instance, so the seeded realm holds two cards. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates workspace favorites to display real realm-index-derived metadata (Cards/Files/Definitions counts plus lifecycle timestamps), adjusts workspace/catalog ordering in the chooser, and introduces a shared menu-item identifier hook for styling without relying on test-only attributes.
Changes:
- Added realm lifecycle timestamps (
createdAt,updatedAt) to the boot-time federated realm info payload, while keeping index-aggregate counts on a separate, lazy path. - Introduced a new realm-server endpoint
/_federated-index-countsand host-side caching/invalidation to load tile counts only for favorited realms. - Updated workspace chooser UI/behavior (favorite tile layout + tooltips, menu footer timestamps, new workspace tile ordering, and navigation expectations) and added
data-menu-item-idto menu items.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/runtime-common/realm.ts | Adds lifecycle timestamps plumbing and index-count aggregation + memoized APIs (getDetailedRealmInfo, getIndexCounts). |
| packages/runtime-common/index.ts | Exports RealmIndexCounts. |
| packages/runtime-common/helpers/const.ts | Adds helpers for asserting/stripping federated-only realm-info extras in tests. |
| packages/realm-server/routes.ts | Registers the new /_federated-index-counts route. |
| packages/realm-server/handlers/handle-realm-info.ts | Switches federated info to use getDetailedRealmInfo() (timestamps included). |
| packages/realm-server/handlers/handle-realm-index-counts.ts | Implements the federated index-counts endpoint backed by Realm#getIndexCounts(). |
| packages/realm-server/tests/server-endpoints/index-counts-test.ts | Adds endpoint-level coverage for counts, auth behavior, and staleness after reindex. |
| packages/realm-server/tests/server-endpoints/info-test.ts | Updates federated-info tests to assert presence/parseability of timestamp extras. |
| packages/realm-server/tests/server-endpoints/user-and-catalog-test.ts | Updates catalog-realms test expectations to match plain /_info payload shape. |
| packages/realm-server/tests/realm-endpoints/info-test.ts | Adds realm-level assertions for counts bucketing and timestamps; guards that /_info stays lean. |
| packages/realm-server/tests/index.ts | Registers the new server-endpoints test module. |
| packages/realm-server/tests/helpers/index.ts | Adds shared assertion helpers for realm-info extras and index-counts payloads. |
| packages/host/app/services/realm.ts | Adds tracked, lazy index-count loading/caching + invalidation on index events; threads timestamps into default realm info objects. |
| packages/host/app/services/realm-server.ts | Adds fetchRealmIndexCounts() client method for the new endpoint. |
| packages/host/app/components/operator-mode/workspace-chooser/index.gts | Adds createdAt-based sorting, lazy count-loading modifier, responsive favorite tile sizing, and updated selection/nav model. |
| packages/host/app/components/operator-mode/workspace-chooser/workspace.gts | Renders enlarged favorite tiles with stats/tooltips, adds timestamps footer in menu, and updates styling/hover behavior. |
| packages/host/tests/acceptance/workspace-chooser-test.gts | Expands acceptance coverage for tooltips, favorite-tile stats behavior, tile ordering, and keyboard navigation changes. |
| packages/host/tests/unit/workspace-timestamp-labels-test.ts | Adds unit coverage for relative-time formatting helpers. |
| packages/host/app/components/operator-mode/submode-layout.gts | Updates top-bar center layout and avatar border token usage. |
| packages/boxel-ui/addon/src/components/menu/index.gts | Adds data-menu-item-id attribute to menu item content for stable per-item styling hooks. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…omment The divider's hardcoded #e8e8e8 is exactly --boxel-200, so this swap is byte-identical in output while going through the palette like the rest of the chrome. The favorite-tile metadata module's comment still described the counts as coming from each realm's /_info, which stopped being true when they moved to /_federated-index-counts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Summary
Cards/Files/Definitionscounts pulled from the realm index, replacing the previous card-count/recent-activity stats and the collaborator avatar stack.getFileCount/getDefinitionCountqueries toRealmalongside the existinggetCardCount, and threaded the newRealmInfofields through.data-menu-item-idattribute to the sharedMenucomponent so per-item CSS no longer has to select on a test-onlydata-test-*attribute.Test plan
eslintandember-template-lintpass on all changed files