# 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 `
` 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 │ │ │ └─────────────────────────────────────────────────────────────────────┘ ``` ### 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 1. 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. 2. 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. 3. A "Load 10 more mists" control appends the next page in place via HTMX — no full page reload — exactly mirroring the existing `fleet_more` pattern on this same page. It disappears once all mists are loaded. 4. Zero-mist users see a clear, friendly empty state with a `muse mist create` hint, not a blank gap. 5. A "View all mists →" link routes to the existing full `/{owner}/mists` browse page. 6. Works identically for human and agent profile handles (mirrors `_fetch_repos`'s existing human/agent branching). 7. 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 in `ui_user_profile.py`, mirroring `_fetch_agent_fleet`'s signature/return shape. Delegates to the existing `musehub_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 route `GET /{handle}/mists-more` (distinct from the existing full-page `/{owner}/mists` route — verify no path collision at router-registration time), mirroring `fleet_more` exactly: resolves identity, 404 if missing, calls `_fetch_recent_mists` with the given cursor, renders `musehub/partials/mist_preview_cards.html`. - [ ] `PMC_03` — Wire `profile_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_fleet` calls. **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 template `musehub/partials/mist_preview_cards.html` — compact card grid (not the full-width `.ms-row` style), each card showing: `_type_color` left accent bar, artifact-type chip, filename (monospace), title (2-line clamp, only if set), agent badge (only if `agent_id` set), language badge, `created_at | fmtrelative`, fork_count/view_count with icons. Whole card is an `` to `/{owner}/mists/{mist_id}`. - [ ] `PMC_05` — New `
` block in `profile.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 when `mists_next_cursor` is 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 the `muse mist create ` 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 on `claude-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}/mists` browse 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/search` 500 bug (Jinja template-caching `TypeError` on any query with real results) — unrelated to mists, tracked as a known issue, not fixed here.