Add standalone (not-signed-in) mode - #44
Conversation
Let the app run without an Onshape login: opening it directly serves the read-only library UI backed by the loaded database. Everything works except Onshape-dependent actions, which are hidden client-side and guarded server-side behind sign-in. - Backend: `isSignedIn` util + `requireSignInMiddleware` mirroring the access-level helper, applied to favorites, insert, user-data, and live-preview thumbnail routes. Service factory tolerates not-signed-in callers; a new `FORCE_SIGNED_IN` testing var forces sign-in with a fake user. - `/context-data` exposes `signedIn`; not-signed-in returns default settings. - Frontend: `RequireSignIn`/`useIsSignedIn` gate the insert and favorites UI; the units call is skipped (default units), the preview falls back to the static thumbnail, Onshape messaging is a no-op, settings persist to localStorage, and a `/` route provides a direct entry point.
Make unitInfo optional in the configuration UI: when the document's units aren't available (not signed in), each quantity parameter renders in its own default unit (parameter.unit) with a default precision, instead of a global mm/deg fallback. Signed-in behavior is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HFS93Qu7RahhGxZmCS78MA
Show a "Sign in" button in the navbar when not signed in. It kicks off the existing Onshape OAuth flow (/auth/sign-in) and returns the user to their current location; on reload context-data reports them signed in, so no callback query-param plumbing is needed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HFS93Qu7RahhGxZmCS78MA
Standalone sign-in doesn't pass sessionCompanyId, so leave company_id off the Onshape authorize URL and let the user pick their account. Onshape-launched sign-in still scopes to the provided company. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HFS93Qu7RahhGxZmCS78MA
…testing-g3qchq # Conflicts: # src/__test_utils__/test-app.ts # src/backend/routes/thumbnails.ts # src/backend/routes/user.test.ts # src/backend/routes/user.ts # src/backend/services.ts # src/frontend/app/app-navbar.tsx # src/frontend/insert/thumbnail.tsx # src/frontend/queries.ts # src/frontend/routeTree.gen.ts # src/frontend/routes/app/library/$libraryId/index.tsx # src/frontend/settings/settings.ts # src/shared/types.ts
Shorten multi-line doc comments, drop redundant/what-narrating comments, and fix a stale context-data reference. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HFS93Qu7RahhGxZmCS78MA
- sign-in-utils: use HttpStatus.UNAUTHORIZED instead of a raw 401. - Move useIsSignedIn/RequireSignIn into access-level.tsx (next to useAccessData/ RequireAccessLevel) and delete the mixed hook+component sign-in.tsx. - Fix useFavoritesQuery to present empty favorites when not signed in — the endpoint 401s and cards/insert menu gate on !favorites, so the library and insert menu were blank for not-signed-in users. - Insert menu when not signed in: always show the stored preview image (no "sign in to preview" error), toast on open, and disable the insert button instead of hiding it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HFS93Qu7RahhGxZmCS78MA
- Add useIsConnectedToOnshape (documentId/instanceId/elementId present) and gate document-dependent behavior on it instead of sign-in: insert/derive + quick insert, unit-info fetch, and Onshape postMessage. Fixes the signed-in-but-not- in-a-document case. - unit-info: enabled on connection + a default placeholder so the config dialog renders immediately; drop the unit-info pending/error branches. - FORCE_SIGNED_IN is now ignored in production (shared isForceSignedIn helper). - Lift RequireSignIn above FavoriteButton/FavoriteInsertableItem at call sites so the components don't mount when not signed in. - Add a Sign in action button to the preview toast (shared startSignIn helper, also used by the navbar button). - Entry route: readLocalSettings now carries defaults; fix the /init comment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HFS93Qu7RahhGxZmCS78MA
Quantity parameters render in their own default unit whenever the document's units aren't available (not connected to a document, or units no longer fetched), instead of a global mm/deg placeholder. Making the UnitInfo fields optional lets this fallback be the natural path and eases removing custom units later. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HFS93Qu7RahhGxZmCS78MA
AlexKempen
left a comment
There was a problem hiding this comment.
See comments, changes should be pretty straightforward.
| export const favoriteRoutes = getApp(); | ||
|
|
||
| // Favorites are per-user and require a signed-in Onshape session. Scoped by | ||
| // path: a bare use() would apply to every route mounted at /api, not just these. |
There was a problem hiding this comment.
Clean up commment
There was a problem hiding this comment.
These seem like they should just be in the route handlers like the other functionality, a bit weird to have it standalone out here
| it("GET /access-data reports signedIn: false when not signed in", async () => { | ||
| const app = createTestApp({ | ||
| signedIn: false, | ||
| accessLevel: AccessLevel.USER | ||
| }); |
There was a problem hiding this comment.
Let's rename sign-in.tsx to something more semantic
| * The caller's access, which nothing waits for — editor affordances appear late. | ||
| * The settings menu can view the app as a lower level (or a higher one, up to | ||
| * the granted max); that choice is local state, so refetching the query — which | ||
| * every navigation does — can't revert it. |
There was a problem hiding this comment.
Cleanup/trim all comments according to agents.md
| */ | ||
| export function useAccessData(): AccessData { | ||
| return useQuery(getAccessDataQuery()).data ?? DEFAULT_ACCESS_DATA; | ||
| const serverData = useQuery(getAccessDataQuery()).data; |
There was a problem hiding this comment.
I think accessData should just get moved out of getAccessDataQuery entirely so it only returns the max access level. We can then store the current access level in local storage like the other params.
For the defaulting logic let's just make default access level a client visible env variable (like VITE_* I think?) and apply it on the client.
| */ | ||
| export function useIsConnectedToOnshape(): boolean { | ||
| const search = useSearch({ strict: false }); | ||
| return Boolean(search.documentId && search.instanceId && search.elementId); |
There was a problem hiding this comment.
Let's use isElementPath on search
|
|
||
| export function HeartBrokenIcon(): ReactNode { | ||
| return <IconHeartBroken size={IconSize.SMALL} color={HeartIconColor} />; | ||
| export function HeartBrokenIcon(props: { size?: number }): ReactNode { |
There was a problem hiding this comment.
Let's have an explicit props interface, do:
const { size = IconSize.SMALL } = props;
| configuration: ParameterValues; | ||
| setConfiguration: Dispatch<ParameterValues>; | ||
| unitInfo: UnitInfo; | ||
| unitInfo?: UnitInfo; |
There was a problem hiding this comment.
I think unitInfo is guaranteed but the values inside it are optional now? At least that would be my expectation if we were using placeholder/default value correctly with the unit info query
| ...getFavoritesQuery(libraryId), | ||
| enabled: signedIn | ||
| }); | ||
| return signedIn ? query : { ...query, data: EMPTY_FAVORITES }; |
There was a problem hiding this comment.
IIRC there should be default data or other tanstack query option that makes this unnecessary maybe? But also we shouldn't be displaying favorites at all when not signed in so we shouldn't even be calling useFavoritesQuery unneccessarily
| return useQuery(getJobStatusQuery(libraryId)); | ||
| const { signedIn, currentAccessLevel } = useAccessData(); | ||
| const enabled = signedIn && hasEditorAccess(currentAccessLevel); | ||
| return useQuery({ ...getJobStatusQuery(libraryId), enabled }); |
There was a problem hiding this comment.
Pass in enabled to getJobStatusQuery defaulting to true rather than spreading it into the useQuery, ditto for useFavorites I think
| return accessLevel === AccessLevel.USER; | ||
| } | ||
|
|
||
| const ACCESS_LEVEL_RANK: Record<AccessLevel, number> = { |
There was a problem hiding this comment.
I think this helper can also be used in one or two other places we check access level today
- favorites: apply requireSignInMiddleware per route handler, drop the standalone .use() block and its comment. - useIsConnectedToOnshape: use isElementPath(search). - favorite-button: type icon size as IconSize; explicit HeartBrokenIcon props with a default. - unit info: guaranteed object via an EMPTY_UNIT_INFO placeholder, values stay optional and fall back to each parameter's own unit. - queries: getFavoritesQuery/getJobStatusQuery take an enabled param; favorites use placeholderData instead of the spread hack. - access data: /access-data returns only maxAccessLevel + signedIn; the viewed level is client-side, defaulting from VITE_DEFAULT_ACCESS_LEVEL; trim comments; reuse isWithinAccessLevel in the access-level select. - sign-in confirmation toast: use a redirect search param instead of sessionStorage. - rename the not-signed-in route test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HFS93Qu7RahhGxZmCS78MA
ab500e0 to
1e42cb2
Compare
Let the app run without an Onshape login: opening it directly serves the
read-only library UI backed by the loaded database. Everything works except
Onshape-dependent actions, which are hidden client-side and guarded server-side
behind sign-in.
isSignedInutil +requireSignInMiddlewaremirroring theaccess-level helper, applied to favorites, insert, user-data, and live-preview
thumbnail routes. Service factory tolerates not-signed-in callers; a new
FORCE_SIGNED_INtesting var forces sign-in with a fake user./context-dataexposessignedIn; not-signed-in returns default settings.RequireSignIn/useIsSignedIngate the insert and favorites UI;the units call is skipped (default units), the preview falls back to the
static thumbnail, Onshape messaging is a no-op, settings persist to
localStorage, and a
/route provides a direct entry point.