feat(app): add "Your projects" view for accessible org & personal packs - #81
Merged
Merged
Conversation
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>
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>
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>
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>
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>
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>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ 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.
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

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/archivedprojects, 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-lib—get_user_projects()callsGET /v3/user/{id}/all-projectswith 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.mr-auth|get_user_projectscommand and agetUserProjects()frontend helper.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: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'sfrontendDistcodegen, 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 byyour_projects_tab) that loads personal and org projects—including private/unlisted—via a newGET /v3/user/{id}/all-projectspath exposed asmr-auth|get_user_projects.Auth plumbing:
AuthProvidergains optionalrefreshSession; the app wires it tofetchCredentialsso pages like Your projects can reconcile when the backend has no active session.Session-sensitive data: On login finish and logout/session removal,
app-libpurgesAUTHENTICATED_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), taggednonPublic, stripped on auth changes, and the list reloads whensession_tokenchanges.Reviewed by Cursor Bugbot for commit df6e69e. Bugbot is set up for automated code reviews on this repo. Configure here.