mirror of
https://github.com/simstudioai/sim.git
synced 2026-10-12 23:33:47 +08:00
* feat(workspaces): pin workspaces and widen the switcher to six rows Show up to six workspaces in the switcher instead of three, keeping the search input from six onward so it appears exactly when the list fills. Pin workspaces to the top of the switcher via the existing row context menu. Pins are per-user and global, so they live on the user's settings row rather than in `pinned_item`, which scopes every row to one workspace. They ride along on the /api/workspaces payload the switcher already loads, so the server prefetch hydrates them and pinned-first ordering never re-sorts after hydration. Drop the seat/workspace-migration disclosure copy from both invitation accept surfaces. The accept-time disclosure tokens are unchanged, so the server still verifies the outcome hasn't shifted since the page loaded. * fix(workspaces): serialize pin writes so a rapid toggle cannot be undone Each write carries the whole pin list, so two overlapping requests that the network delivered out of order left the earlier click as the stored state. Chain them instead, and hold reconciliation until the last queued write settles — refetching between two writes rendered the server's intermediate state and bounced the row out of the pinned group and back. * refactor(workspaces): store workspace pins in pinned_item, not user settings Workspace pins were a jsonb array on the settings row, replaced wholesale on every toggle. That shape is what forced the write serialization in53ee94f5: two overlapping toggles each sent the entire list, so the one that landed last won regardless of which the user clicked last. pinned_item is the canonical pinning table and its resource_type is plain text precisely so kinds can be added without a migration, so `workspace` joins it as a sixth kind. A pin is now one row: pinning inserts, unpinning deletes, and two toggles touch different rows and cannot overwrite each other. The serialization, the outstanding-write counter, the settings column, and its migration all go away, and deleting a workspace now cascades its pins. Reads stay on the /api/workspaces payload — the switcher needs the pins *of* every workspace, not the pins *inside* one — so the sidebar prefetch still hydrates them and pinned-first ordering is correct on first paint. * fix(workspaces): serialize same-workspace pin toggles and tolerate replays Splitting pins into rows removed the lost-update race between *different* workspaces but not the one on a single row: pin then unpin the same workspace and the DELETE could overtake its INSERT, delete nothing, and leave the workspace pinned. A mutation scope serializes them; TanStack runs onMutate before the scope gate, so the optimistic update is still immediate. Both duplicate-click replays now resolve to their end state rather than erroring — a repeat pin answers 409, a repeat unpin 404, and each means the row is already how the caller wants it. Rollback undoes its own toggle instead of restoring a snapshot, so a sibling toggle's optimistic state survives. Also: cap the switcher to the height Radix measured, since six rows can push the footer actions off a short viewport with nothing able to scroll to them; drop a dead pinned-item invalidation and a redundant ref; return the pin set from the hook to match usePinnedIds; and exclude workspace pins from the unscoped pinned-items listing, where they would read as a resource inside themselves. * fix(workspaces): hold pin reconciliation until nothing is still queued The mutation scope serializes the writes, so an earlier toggle settles while a later one is still waiting its turn. Invalidating there refetched the server's intermediate state and bounced the row out of the pinned group and back before the last write had even left the client. * fix(workspaces): count outstanding pin toggles off the mutation cache `hooks/queries/workspace.ts` has no 'use client' directive because server code imports `workspaceKeys` during SSR, so the `useRef` counter added in3dc924dabroke the production build — caught by CI, not by typecheck or tests. `isMutating` answers the same question without a hook: `onSettled` runs before the mutation leaves `pending`, so it counts itself, and anything above one means a later toggle is still queued behind the scope.