Constraints agreed in the design comments

    Header cluster order: Explore - Chats - Notifications - Activity Feed - Bookmarks - Avatar.

    Icons only, no text labels, tooltips for naming.

    Bookmarks is desktop only, the web cluster omits it. Chats is a separate project so I will leave a slot

    Clicking the icon scopes Explore to the currently open space. All spaces is an opt-in widening.

Implementation steps

    Header button and breadcrumb

    Add the Explore icon to the top-right cluster in packages/ui/src/site-header.tsx, beside the existing Activity Feed button. Wire it to {key: 'explore', context: {type: 'site', id: <current space>}}. Add the Home > Explore breadcrumb to the page.

    Desktop gets the same icon in its header, same route.

    All-spaces scope and its route

      routeToUrl returns null for context.type !== 'site'. Return /hm/explore and query string for the node case. routeToHref delegates here, so verify it stops inheriting the null.

      hypermediaUrlToRoute: add an early match for /hm/explore returning context: {type: 'node'}.

      New route apps/web/app/routes/hm.explore.tsx to /hm/explore?q=…&sort=…. Copy the loader from hm.notifications.tsx plus a meta for title and favicon. Body is client-only.

      Existing <space>/:explore URLs keep working. Scope switching is then onScopeChange flipping context, which the scope pill already supports.

      The reason is that the node scope is in the route schema but has no web URL, so "All spaces" is unreachable until this exists.

    Filter chip UI

    Replace the query string bar in <ExplorePage> with the design's chip row: All spaces / All types / In / Author / Publication date / See all filters, plus the Sort dropdown.

    Build the chips as views over the existing AST - exploreQueryChips, toggleExplorePredicate, removeExploreQueryChip, clearExploreConditions and cycleExploreSort are already exported from packages/shared/src/explore.ts. Keep the raw query string behind "See all filters" as the power path.

    Facet mapping:

    Chip

    Maps to

    Work

    All types

    type: predicate

    UI only for document/block/comment

    In

    in: path-prefix scope

    UI only

    All spaces

    HMExploreContext / space: scope

    One space at a time is free. multi select needs a decision

    Publication date

    Comparison on the displayPublishTime attribute

    Works, but as an ISO string compare. ranges need care

    Author

    DocumentFilter has no author predicate

    Backend work needed so ship without it

    Sort

    sort: presentation directive

    UI only

    Spaces and People facets

    Add 'space' to HMExploreResultType and to the resultTypes set so type:space parses. Map ['space'] to [EntityKindFilter.ENTITY_KIND_SPACE] in entityKindFilters(). In assembleExploreResults, classify a document result with no path as a space. Add the tab to tabs in explore-page.tsx.

    Add contact the same way, letting it through contentTypeFilters() / entityKindFilters().

    The daemon already indexes both. ENTITY_KIND_SPACE is in the default entityKindFilters() list today, so spaces are already in the result set, just rendered as ordinary documents. Contacts likewise (CONTENT_TYPE_CONTACT, ENTITY_KIND_CONTACT), and api-search.ts already maps type: 'contact'.

    One design question: a space row probably wants its own presentation: space icon, member count, document count, rather than the document row.

    Landing state (empty query)

    Two blocks, both built on the row anatomy from the old desktop Library page:

      Your Spaces - spaces the account joined, from contacts with subscribe.site: true. Card: icon, name, role badge, document count, last updated. Counts and times from queryDocuments scoped with SpaceMatch. the role badge from useIsSiteOwner.

      Recently Updated Documents - breadcrumb, title, FacePile, comment/reference counters, and LibraryEntryUpdateSummary (packages/ui/src/activity.tsx). apps/desktop/src/models/library.ts has the shape and the comment-hydration trick, but calls grpcClient directly. The web version must go through useUniversalClient(). apps/web/app/local-db-recents.ts is a good secondary source for a "Jump back in" strip.

    The "Jump to:" chip row above them just sets the type facet on an empty query.

    Rows must carry PrivateBadge and respect visibility, as the Library rows did.

    Per-facet counts

    The design shows exact counts (Documents 51, Text Blocks 11). assembleExploreResults computes counts from loaded pages after client-side filtering, so the numbers grow as you scroll and the intersection path caps at EXPLORE_INTERSECTION_DOCUMENT_CAP = 1000.

    We can ship approximate counts first, but needs backend proto change for accurate count.

Questions

    Media and citations - no type in the FTS index (title, document, comment, contact, profile, meta only), so they cannot be searched like other facets. I don't know how this can be implemented

    Attributes as a result type - predicates and autocomplete exist but results do not.

    Ask Agent toggle - is this something I should do?

    Multi space selection. Search takes one iri_filter, and searchIriFilter() bails when a query has more than one positive scope. Restrict the facet to one space, do multiple searches and merge client-side, or make iri_filter repeated in the proto?

    Author facet. No daemon predicate exists. Client-side filtering only sees the loaded page.

    Desktop's existing entry points. The Explore results for "…" / Advanced search footer rows in the desktop search popover become a second path once the icon exists. Should we keep them or remove?

Do you like what you are reading? Subscribe to receive updates.

Unsubscribe anytime