gabriel / musehub public
profile-mists-preview-component.md markdown
295 lines 17.6 KB
Raw
sha256:34035d72cef530c1ab9d6a6f53be18d803dde705fc3157617d70352a96d0747b Merge branch 'fix/wire-push-external-parent-manifest' into dev Human 8 days ago

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

  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 <a> to /{owner}/mists/{mist_id}.
  • [ ] PMC_05 — New <section class="profile-section"> 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 <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 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.
File History 3 commits
sha256:34035d72cef530c1ab9d6a6f53be18d803dde705fc3157617d70352a96d0747b Merge branch 'fix/wire-push-external-parent-manifest' into dev Human 8 days ago
sha256:fc04e4cae9e1774d6a21b65c45daeed0e6787eb581d13aa1b03bfe9384a34226 Merge branch 'fix/two-column-scroll-layout' into dev Human 8 days ago
sha256:408916fc5973ba59c6e4eebaa80ebdcc801c0a63205651e25009d11548f79454 chore: bump version to 0.2.0.dev2 — nightly.2, matching muse Sonnet 4.6 patch 11 days ago