Skip to content

Add standalone (not-signed-in) mode - #44

Merged
AlexKempen merged 12 commits into
certfrom
claude/standalone-ui-testing-g3qchq
Aug 18, 2026
Merged

Add standalone (not-signed-in) mode#44
AlexKempen merged 12 commits into
certfrom
claude/standalone-ui-testing-g3qchq

Conversation

@AlexKempen

Copy link
Copy Markdown
Collaborator

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.

claude added 7 commits August 10, 2026 14:00
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
Comment thread src/backend/services.ts Outdated
Comment thread src/frontend/favorites/favorite-button.tsx Outdated
Comment thread src/frontend/insert/configurations.tsx Outdated
Comment thread src/frontend/routes/index.tsx Outdated
Comment thread src/frontend/routes/index.tsx Outdated
claude and others added 4 commits August 17, 2026 19:59
- 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 AlexKempen left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

See comments, changes should be pretty straightforward.

Comment thread src/backend/routes/favorites.ts Outdated
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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Clean up commment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

These seem like they should just be in the route handlers like the other functionality, a bit weird to have it standalone out here

Comment on lines +19 to +23
it("GET /access-data reports signedIn: false when not signed in", async () => {
const app = createTestApp({
signedIn: false,
accessLevel: AccessLevel.USER
});

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Let's rename sign-in.tsx to something more semantic

Comment thread src/frontend/api-utils/access-level.tsx Outdated
Comment on lines +30 to +33
* 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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Cleanup/trim all comments according to agents.md

Comment thread src/frontend/api-utils/access-level.tsx Outdated
*/
export function useAccessData(): AccessData {
return useQuery(getAccessDataQuery()).data ?? DEFAULT_ACCESS_DATA;
const serverData = useQuery(getAccessDataQuery()).data;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Let's use isElementPath on search


export function HeartBrokenIcon(): ReactNode {
return <IconHeartBroken size={IconSize.SMALL} color={HeartIconColor} />;
export function HeartBrokenIcon(props: { size?: number }): ReactNode {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Let's have an explicit props interface, do:
const { size = IconSize.SMALL } = props;

Comment thread src/frontend/insert/configurations.tsx Outdated
configuration: ParameterValues;
setConfiguration: Dispatch<ParameterValues>;
unitInfo: UnitInfo;
unitInfo?: UnitInfo;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

Comment thread src/frontend/queries.ts Outdated
...getFavoritesQuery(libraryId),
enabled: signedIn
});
return signedIn ? query : { ...query, data: EMPTY_FAVORITES };

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

Comment thread src/frontend/queries.ts Outdated
return useQuery(getJobStatusQuery(libraryId));
const { signedIn, currentAccessLevel } = useAccessData();
const enabled = signedIn && hasEditorAccess(currentAccessLevel);
return useQuery({ ...getJobStatusQuery(libraryId), enabled });

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Pass in enabled to getJobStatusQuery defaulting to true rather than spreading it into the useQuery, ditto for useFavorites I think

Comment thread src/shared/types.ts
return accessLevel === AccessLevel.USER;
}

const ACCESS_LEVEL_RANK: Record<AccessLevel, number> = {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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
@AlexKempen
AlexKempen force-pushed the claude/standalone-ui-testing-g3qchq branch from ab500e0 to 1e42cb2 Compare August 18, 2026 05:21
@AlexKempen
AlexKempen merged commit cfcfe21 into cert Aug 18, 2026
2 checks passed
@AlexKempen
AlexKempen deleted the claude/standalone-ui-testing-g3qchq branch August 18, 2026 05:23
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.

2 participants