Recent Mists preview component on user profile pages
Background
The proximate trigger
Mists are the Muse answer to a GitHub gist — a single, forkable artifact
(code, MIDI, a JSON Schema, a Solidity ABI) backed by a real Muse repo
(domain_id="mist") purely as VCS plumbing. Until now, that backing repo
had no domain filter anywhere in repo Explore, so every public mist
appeared in /explore mixed in with real showcase repos — diluting repo
discovery with what are, functionally, gists.
That's now fixed (MusehubRepo.domain_id != "mist" added to
musehub_discover.list_public_repos, search_repos_by_text, and
explore_page's four supporting sidebar/stat queries — landed and
verified live on staging, 4 new regression tests in
tests/test_musehub_discover.py).
What's still missing
Filtering mists out of Explore closes one gap but opens another: there is now no visually meaningful way to browse a specific user's mists from the page people actually land on — their profile. Today:
GET /{owner}/mists— a full dedicated browse page already exists (musehub/api/routes/musehub/ui_mists.py::mist_list_page,mist_list.html), with type-filter chips and cursor pagination. But it's not linked from anywhere on the profile page itself — a visitor has to already know the URL.- The profile page (
ui_user_profile.py::profile_page,profile.html) has dedicated sections for Repositories, Spawned Agents, Activity, and more — each rendered as a<section class="profile-section">block — but nothing for Mists.
gabriel's ask: a compact "Recent Mists" component embedded directly on the profile page — the 10 most recent, paginated ("load more"), and visually meaningful — a user should be able to tell at a glance what each mist is (type, title, language, activity) without clicking through.
Existing precedent to build on (not build from scratch)
This is smaller than it first looks, because most of the hard parts already exist:
| Need | Already exists |
|---|---|
| Owner-scoped, cursor-paginated mist query | musehub_mists.list_mists(db, owner=..., cursor=..., limit=...) |
| "Load more" HTMX pattern on this exact page | ui_user_profile.py::fleet_more (GET /{handle}/fleet) — the Spawned Agents section already does exactly this |
| Rich per-mist metadata for cards | MistListEntry: artifact_type, filename, title, language, agent_id, fork_count, view_count, created_at, primary_symbol |
| Type-color visual language | ui_mists.py::_TYPE_COLORS (code=blue, midi=purple, schema=green, abi=amber, dataset=orange, text=gray, config=teal) |
| Full detail page to link out to | /{owner}/mists/{mist_id} already exists and renders |
This issue is specifically about the profile-page preview widget —
not a new mist browsing page (that exists), not new backend mist CRUD
(that exists). The net-new work is: a fetch helper, an HTMX partial route,
a compact card template, and the section wiring into profile.html.
Design — the shape to build toward
Public-only, matching this page's existing precedent. profile_page
has no auth dependency today — _fetch_repos filters
visibility == "public" unconditionally for every visitor, owner
included. The new mists preview follows the same rule: public mists only,
no secret-mist exposure logic, no new auth wiring on this route. Browsing
your own secret mists stays the job of the existing /{owner}/mists page
(which already does include_secret correctly when authenticated).
Compact cards, not full-width rows. The existing mist_rows.html
fragment (used by both /{owner}/mists and /mists/explore) is a
detailed full-width list — right for a dedicated browse page, wrong for a
profile preview rail. The new component is a compact card grid,
mirroring how GitHub's profile "Pinned repositories" differs visually
from the full repo list. Same underlying data, denser presentation.
ASCII mockup — where it sits on the profile page
┌─────────────────────────────────────────────────────────────────────┐
│ Repositories 12 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ muse │ │ musehub │ │ maestro │ ... │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ Mists 13 total │
│ │
│ ┌────────────────────┐ ┌────────────────────┐ ┌────────────────┐│
│ │▐code │ │▐midi │ │▐schema ││
│ │ │ │ │ │ ││
│ │ mwp-qa.md │ │ theme.mid │ │ token.json ││
│ │ MWP QA — Verifying │ │ "Autumn Reprise" │ │ ERC-8004 ││
│ │ the Wire Protocol │ │ │ │ identity token ││
│ │ │ │ │ │ ││
│ │ 🤖 agent · markdown │ │ gabriel │ │ gabriel ││
│ │ 2 days ago │ │ 5 days ago │ │ 1 week ago ││
│ │ │ │ │ │ ││
│ │ ⑂ 0 ⌕ 20 │ │ ⑂ 3 ⌕ 45 │ │ ⑂ 1 ⌕ 12 ││
│ └────────────────────┘ └────────────────────┘ └────────────────┘│
│ │
│ ┌────────────────────┐ ┌────────────────────┐ ┌────────────────┐│
│ │▐abi │ │▐code │ │▐config ││
│ │ ... │ │ ... │ │ ... ││
│ └────────────────────┘ └────────────────────┘ └────────────────┘│
│ │
│ ┌─────────────────────────┐ │
│ │ Load 10 more mists │ │
│ └─────────────────────────┘ │
│ │
│ View all mists → │
├─────────────────────────────────────────────────────────────────────┤
│ Recent Activity │
│ ... │
└─────────────────────────────────────────────────────────────────────┘
The left edge bar (▐) on each card is a 3px solid block in the mist's
_type_color — the same color language already used on /{owner}/mists
and /mists/explore, so a user who's seen either page recognizes the
mist type instantly without reading the label.
ASCII mockup — single card, annotated
┌────────────────────────────┐
│▐ code │ ← type_color left bar + label chip
│ │
│ mwp-qa.md │ ← filename (monospace)
│ MWP QA — Verifying the │ ← title, if set (2-line clamp)
│ Wire Protocol │
│ │
│ 🤖 agent · markdown │ ← agent badge (if agent_id set) · language
│ 2 days ago │ ← created_at, relative
│ │
│ ⑂ 0 ⌕ 20 │ ← fork_count · view_count, icon+number
└────────────────────────────┘
↑ whole card is a link to /{owner}/mists/{mist_id}
↑ hover: border brightens to type_color, slight lift (existing
.repo-card hover pattern reused for consistency)
ASCII mockup — empty state (zero public mists)
┌─────────────────────────────────────────────────────────────────────┐
│ Mists │
│ │
│ No public mists yet. │
│ │
│ Share a single artifact — code, MIDI, a schema — │
│ with: muse mist create <file> │
│ │
└─────────────────────────────────────────────────────────────────────┘
ASCII mockup — "Load more" in flight (HTMX swap, mirrors fleet_more)
┌─────────────────────────┐
│ ⟳ Loading… │ ← hx-indicator swap
└─────────────────────────┘
↓ on response ↓
[ 10 more cards appended above the button, cursor advances,
button re-renders with the new next_cursor — or disappears
entirely when next_cursor is null (all mists loaded) ]
Goal — definition of done
- A visitor to any user's profile page sees a "Mists" section (public mists only) positioned between Repositories and Recent Activity, showing up to 10 most recent mists as compact cards.
- Each card conveys, at a glance and without a click: artifact type (color + label), filename, title (if set), agent-authored badge (if applicable), language, relative creation time, fork count, view count.
- A "Load 10 more mists" control appends the next page in place via
HTMX — no full page reload — exactly mirroring the existing
fleet_morepattern on this same page. It disappears once all mists are loaded. - Zero-mist users see a clear, friendly empty state with a
muse mist createhint, not a blank gap. - A "View all mists →" link routes to the existing full
/{owner}/mistsbrowse page. - Works identically for human and agent profile handles (mirrors
_fetch_repos's existing human/agent branching). - Every deliverable below is TDD'd: red test written first, then made green.
Phases
Ordered by load-bearing dependency — the data layer must exist before the HTMX route can call it, and the HTMX route must exist before the template can wire a working "load more" button to it.
Phase 1 — Data layer: recent-mists fetch + HTMX load-more route
- [ ]
PMC_01—_fetch_recent_mists(session, identity, *, cursor=None, limit=10)helper inui_user_profile.py, mirroring_fetch_agent_fleet's signature/return shape. Delegates to the existingmusehub_mists.list_mists(db, owner=handle, include_secret=False, cursor=cursor, limit=limit)— no new query logic, no new service function. Returns{"mists": [...], "next_cursor": ..., "total": ...}. - [ ]
PMC_02— New HTMX partial routeGET /{handle}/mists-more(distinct from the existing full-page/{owner}/mistsroute — verify no path collision at router-registration time), mirroringfleet_moreexactly: resolves identity, 404 if missing, calls_fetch_recent_mistswith the given cursor, rendersmusehub/partials/mist_preview_cards.html. - [ ]
PMC_03— Wireprofile_page's main context to include the first 10 mists + total count via_fetch_recent_mists(session, identity, limit=10), alongside the existing_fetch_repos/_fetch_agent_fleetcalls.
Exit gate: _fetch_recent_mists unit-tested against a seeded human
and agent identity (returns correct mists, respects limit, advances
cursor correctly, empty list for a mist-less user). GET /{handle}/mists-more?cursor=... E2E-tested: 404 for unknown handle, 200
with the next page for a known handle, next_cursor correctly null on
the last page.
Phase 2 — Visual component: compact card grid + section wiring
- [ ]
PMC_04— New partial templatemusehub/partials/mist_preview_cards.html— compact card grid (not the full-width.ms-rowstyle), each card showing:_type_colorleft accent bar, artifact-type chip, filename (monospace), title (2-line clamp, only if set), agent badge (only ifagent_idset), language badge,created_at | fmtrelative, fork_count/view_count with icons. Whole card is an<a>to/{owner}/mists/{mist_id}. - [ ]
PMC_05— New<section class="profile-section">block inprofile.html, positioned between the existing Repositories and Recent Activity sections: header (Mists+ total count meta, matching every other section's header pattern), the card grid ({% include "musehub/partials/mist_preview_cards.html" %}), the "Load 10 more mists" HTMX button (hx-get="/{{ handle }}/mists-more?cursor=...",hx-target,hx-swap="beforeend", hidden entirely whenmists_next_cursoris null), and "View all mists →" linking to/{{ handle }}/mists. - [ ]
PMC_06— Empty state: when a profile has zero public mists, the section renders a centered friendly message with themuse mist create <file>hint instead of an empty grid — never a blank gap that looks broken.
Exit gate: GET /{handle} E2E-tested for: a user with >10 mists (first
10 render, "Load more" present), a user with exactly 10 (button absent,
next_cursor null), a user with 0 (empty state renders, no button, no
grid), and an agent handle (section renders using the same helper).
Visual smoke-check in a real browser against staging — cards render with
correct type colors, hover state matches the existing repo-card lift/border
pattern, "Load more" appends cards in place with no page reload or flash.
Phase 3 — Live verification against staging
- [ ]
PMC_07— Deploy to staging and verify against gabriel's real 13 mists: profile page shows 10, "Load more" reveals the remaining 3 and then disappears, cards visually match the mockup (type colors, agent badge onclaude-code-authored mists, relative timestamps correct). - [ ]
PMC_08— Verify a fresh/mist-less profile shows the empty state correctly on real staging, not just in tests.
Exit gate: Live, in-browser confirmation on https://staging.musehub.ai
against real data — not just the test suite.
Acceptance criteria (whole-issue gate)
- Every human and agent profile page renders a Mists section (public mists only) between Repositories and Recent Activity.
- Cards convey type, filename/title, language, agent provenance, age, and fork/view counts without requiring a click.
- "Load more" works via HTMX with no full-page reload, mirroring the existing Spawned Agents pattern exactly.
- Zero-mist profiles show a clear empty state, never a blank gap.
- Full TDD coverage: red-then-green tests for the fetch helper, the HTMX partial route, and the profile page's rendering in all three states (has-more, exactly-full-page, empty).
- Verified live on staging with gabriel's real mist data, not just unit tests.
Out of scope (explicit, for future issues)
- Showing secret mists to the authenticated owner on their own profile —
would require adding an auth dependency to
profile_page, which has none today; a separate, larger change with its own design questions (how does that interact with agent-viewed-as-human-visitor caching, etc.). - Any change to the existing full
/{owner}/mistsbrowse page or/mists/explore— both already work correctly and are out of this issue's scope. - Reordering or redesigning any other existing profile section.
- The pre-existing, separately-discovered
GET /explore/search500 bug (Jinja template-cachingTypeErroron any query with real results) — unrelated to mists, tracked as a known issue, not fixed here.