Skip to content

feat(app): add "Your projects" view for accessible org & personal packs - #81

Merged
ChipWolf merged 8 commits into
mainfrom
claude/launcher-modrinth-pack-access-e4c2fa
Jul 17, 2026
Merged

feat(app): add "Your projects" view for accessible org & personal packs#81
ChipWolf merged 8 commits into
mainfrom
claude/launcher-modrinth-pack-access-e4c2fa

Conversation

@ChipWolf

@ChipWolf ChipWolf commented Jul 16, 2026

Copy link
Copy Markdown
Member

What

Adds a Your projects view to the launcher that lists every project the signed-in user can access — their own projects plus every project owned by an organization they belong to — including non-public statuses such as unlisted and private.

Reachable from a new sidebar entry (/your-projects).

Why

The launcher previously only surfaced Modrinth projects through the public search index. That index only contains approved/archived projects, so a user could never see (or easily reach) an org's private packs in the app, even when they're a member with full access. This adds the missing discovery surface.

How

  • app-libget_user_projects() calls GET /v3/user/{id}/all-projects with the active credentials attached (the shared fetch helper adds auth automatically). It returns { projects, organizations } in one call. It is fetched fresh on every call rather than cached, so results always reflect the current auth state — private packs appear the moment you sign in, with no stale logged-out list lingering.
  • Tauri — exposed via a new mr-auth|get_user_projects command and a getUserProjects() frontend helper.
  • UI — a new page groups projects by organization (plus a Personal group) and badges each non-public project (Private, Unlisted, In review, …) so members can spot them at a glance. Cards link to the existing project page, where the authenticated fetch + install flow already works for private projects — no install logic was duplicated.

Visibility behavior (worth a reviewer's attention)

The endpoint filters via filter_visible_projects(hide_unlisted = true), so:

  • Members get the org's public + archived + unlisted + private projects.
  • Non-members (and signed-out users) get only public/archived — identical to the org's public website page.

So private packs surface only for people who actually have access; there is no leak to outsiders.

Testing

  • cargo check -p theseus — clean.
  • cargo check -p theseus_gui — Rust compiles (fails only later in tauri's frontendDist codegen, which needs the built frontend; unrelated to this change).
  • pnpm prepr:frontend:app — 11/11 tasks pass (lint, typecheck, build).

Not yet exercised: a live end-to-end run against a real account with a private org pack — worth a manual smoke test on someone with membership in an org that has a private project.

🤖 Generated with Claude Code


Note

Medium Risk
Touches auth session lifecycle, authenticated API access, and cache purging on login/logout—important for preventing private project metadata leakage, but changes are scoped and defensive.

Overview
Adds a Your projects page (/your-projects, gated by your_projects_tab) that loads personal and org projects—including private/unlisted—via a new GET /v3/user/{id}/all-projects path exposed as mr-auth|get_user_projects.

Auth plumbing: AuthProvider gains optional refreshSession; the app wires it to fetchCredentials so pages like Your projects can reconcile when the backend has no active session.

Session-sensitive data: On login finish and logout/session removal, app-lib purges AUTHENTICATED_CACHE_TYPES (projects, versions, orgs, etc.) so cached private metadata cannot leak across accounts or after sign-out.

Home featured modpacks: Featured packs missing from public search are hydrated with authenticated get_project (cache bypass), tagged nonPublic, stripped on auth changes, and the list reloads when session_token changes.

Reviewed by Cursor Bugbot for commit df6e69e. Bugbot is set up for automated code reviews on this repo. Configure here.

Adds a launcher surface that lists every project the signed-in user can
access — their own projects plus every project owned by an organization
they belong to — including non-public statuses such as unlisted and
private. This does not rely on the public search index (which only
contains approved/archived projects), so private packs a member has
access to now show up.

- app-lib: get_user_projects() fetches /v3/user/{id}/all-projects with
  the active credentials attached. It is fetched fresh on every call
  rather than cached, so results always reflect the current auth state
  (private packs appear as soon as you sign in, with no stale
  logged-out list lingering).
- Exposed via a new mr-auth Tauri command and getUserProjects() helper.
- New Your projects page groups projects by organization (plus a
  Personal group) and badges each non-public project (Private,
  Unlisted, In review, ...) so members can spot them at a glance. Cards
  link to the existing project page, where authenticated install
  already works for private projects.
- Wired up route (/your-projects) and a sidebar nav entry.

Private packs remain visible only to members; a signed-in non-member
sees only an org's public/archived projects, matching the website.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread packages/app-lib/src/api/mr_auth.rs
Comment thread apps/app-frontend/src/pages/YourProjects.vue
Comment thread apps/app-frontend/src/pages/YourProjects.vue Outdated
Addresses review feedback on the Your projects view:

- app-lib: get_user_projects now uses get_and_refresh instead of
  get_active, matching get_credentials. An expired session is refreshed
  (and persisted) before the request, so the fetch layer reads a fresh
  token instead of hitting the API with a stale one.
- Frontend: drive the page off the reactive auth provider (injectAuth)
  instead of a one-shot onMounted fetch. It now reloads on sign-in and
  clears fetched project metadata immediately on sign-out, so private
  and unlisted projects don't linger after logout.
- Frontend: a failed fetch now shows a distinct error state with a retry
  action rather than masquerading as an empty "no access" list or
  leaving stale data on screen.
- Frontend: sign-in goes through the app's shared auth flow
  (requestSignIn) so the reactive credentials update and the list
  refreshes automatically.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread apps/app-frontend/src/pages/YourProjects.vue
Comment thread apps/app-frontend/src/pages/YourProjects.vue
ChipWolf and others added 2 commits July 16, 2026 22:42
Addresses two race/consistency edge cases in the load path:

- A load whose request is still in flight when the user signs out (or
  switches account) no longer repopulates the list. Each load captures a
  request id; the response is discarded if the id is superseded, and
  sign-out invalidates any in-flight load, so private/unlisted metadata
  can't land on a signed-out screen.
- When get_user_projects resolves to null (the backend cleared an
  expired session server-side via get_and_refresh while the frontend
  credentials ref still lags), the page now shows the signed-out
  sign-in prompt instead of an empty "no projects yet" state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The home screen hydrates the curated featured4.json list through the
search index, which only contains public (approved/archived) projects. A
featured pack that is private or unlisted was therefore dropped without
any error, so members who could actually access it never saw it in the
picker they already use.

Featured IDs missing from the search hits are now hydrated directly via
get_project. That request is authenticated, so a non-public pack
resolves for members who can see it and fails harmlessly for everyone
else, leaving the public behaviour unchanged. The author line for those
entries is resolved from the owning organization, since search hits
carry an author but the project object does not.

Also puts the Your projects sidebar entry behind a your_projects_tab
feature flag, defaulted off. The home screen picker is the launcher's
pack surface, so the page stays available (route + settings toggle)
without claiming permanent sidebar space.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread apps/app-frontend/src/pages/YourProjects.vue
Comment thread apps/app-frontend/src/pages/Index.vue
Addresses review feedback:

- The home screen loaded featured packs once at startup, so a user who
  signed in while sitting on it never saw the private packs they had
  just gained access to. It now reloads when the session changes.
  Hydrating non-public featured packs also bypasses the project cache,
  since whether they resolve depends on the session in effect: a copy
  cached while signed in must not survive into a signed-out view.
  Overlapping loads are guarded by a request id.
- When get_user_projects returns null because the session died
  server-side, Your projects now reconciles the app-wide auth state
  instead of only flagging itself, so the rest of the UI stops showing
  the user as signed in. This adds an optional refreshSession hook to
  the shared auth provider, wired to the app's existing
  fetchCredentials. The local flag remains as a fallback when no hook is
  provided.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread apps/app-frontend/src/pages/Index.vue
Comment thread apps/app-frontend/src/pages/Index.vue
Addresses review feedback:

- The home screen reload on sign-out is async, so featured packs from
  the previous session stayed on screen until it finished. Entries the
  search index wouldn't return are now marked and dropped synchronously
  the moment the session ends, keeping public packs in place rather than
  blanking the whole list. The selection falls back to a still-visible
  pack if the selected one is dropped.
- A fetch is written to the on-disk cache regardless of cache behaviour,
  so CacheBehaviour::Bypass avoided reading stale data but still
  persisted private project metadata, which outlived the session that
  was allowed to see it. Signing out now purges the cache types that can
  hold authenticated data (projects, versions, teams, organizations).
  Doing this in app-lib's logout covers every sign-out path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread apps/app-frontend/src/pages/Index.vue
Comment thread packages/app-lib/src/api/mr_auth.rs Outdated
Addresses review feedback. Both gaps were the same oversight: sign-out
was handled as the only way the active account changes, but signing in
replaces it directly without logout ever running.

- Signing in now purges the session-sensitive cache types too. upsert
  deactivates any other account, so authenticating as a second user
  previously left the first user's cached private project metadata on
  disk for the new session to read. The purge stays at the two identity
  change sites rather than in upsert, since get_and_refresh also upserts
  and must not purge on a routine token refresh.
- The home screen dropped non-public featured packs only when the token
  became empty, so switching straight from one account to another kept
  the previous account's private packs on screen until the async reload
  finished. It now drops them on any token change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d3d65ce. Configure here.

Comment thread packages/app-lib/src/api/mr_auth.rs
A session can also end without anyone signing out: get_and_refresh
removes the credentials when a refresh fails, which left cached private
project metadata on disk after the session it belonged to had ended.

Rather than patch that third call site, the purge now lives in
ModrinthCredentials::remove, which is the only way credentials are
removed and covers both sign-out and refresh failure. Sign-in keeps its
own purge because it deactivates another account without removing it,
making it the one identity change that doesn't go through remove.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ChipWolf
ChipWolf merged commit d272c32 into main Jul 17, 2026
5 checks passed
@ChipWolf
ChipWolf deleted the claude/launcher-modrinth-pack-access-e4c2fa branch July 17, 2026 19:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant