Resolves critical advisory GHSA-5xrq-8626-4rwp. Updated store.test.ts
to use vi.useFakeTimers/vi.setSystemTime instead of the constructor spy
pattern that Vitest 4 no longer supports.
Keep Database, Storage, Auth, and Functions as nav tiles — icon + label
only. Stats were consistently reported as inaccurate; nav value is
preserved. Mobile layout uses a 2×2 grid instead of a vertical stack.
- New updateOAuth2Server.svelte card in project settings
- Enable/disable toggle, authorization URL, scopes, per-client token
durations with unit selectors, PKCE enforcement
- min={1} on all duration inputs, scopes sent directly to allow clearing
- Add ProjectUpdateOAuth2Server analytics event
Note: awaiting SDK with updateOAuth2Server method and correct enum names
The project list was only fetched after a row appeared, causing a
~1 second blank dropdown. Now the API call fires the instant the
ProjectAccessSelector mounts (when the user switches to "Specific
projects"), so data is ready or in-flight before any interaction.
All rows share a single Promise for unfiltered loads via prefetchPromise;
typed searches still hit the API individually. The generation-counter
race guard still applies so concurrent awaits can't overwrite each other.
Two bugs in the project access selector:
1. Race condition: loadProjects had no cancellation guard. Two in-flight
requests for the same row (e.g. rapid typing) could resolve out of
order, leaving stale results visible. Added a per-row generation
counter — responses are discarded if a newer request has been
dispatched since they were started.
2. Edit-mode UUIDs: when the edit modal opens with a member's existing
project-specific roles, rowOptions starts empty so Input.ComboBox
falls back to displaying the raw projectId. A $effect now eagerly
calls loadProjects for any row that has a projectId but no loaded
options, resolving the label as soon as the dropdown mounts.
When a project was selected in one row, other rows' cached option lists
still included it, allowing duplicate project assignments. Two places
needed cache busting:
- `onProjectSelected(i)`: clears options for all rows except the one
that just made a selection; they reload fresh (with takenIds applied)
on next focus.
- `removeRow(i)`: clears all sibling caches after removal so the
freed-up project reappears in other rows' dropdowns on next open.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Two root causes of the shift were identified and fixed:
1. Melt UI's `usePopper` applies `position: absolute` to the dropdown
via floating-ui after a `tick()` delay. During that one frame the
`ul` was briefly in normal document flow, momentarily growing the
`<dialog>` element and causing a reflow. Pre-setting
`[data-melt-combobox-menu] { position: absolute }` in base.css
removes the element from flow immediately at mount.
2. Melt UI's ComboBox has `preventScroll: true` by default. On open it
calls `removeScroll()` which sets `body.overflow: hidden` and adds
compensatory `padding-right` for the scrollbar width — a body
reflow that shifts the dialog. Pre-setting `data-melt-scroll-lock`
on the body in `modal.svelte` tells Melt UI the lock is already
active so it returns early without touching the body.
Also fix `on:search` on `Input.ComboBox` (which the component never
dispatches) by replacing it with native `oninput`/`onfocusin` handlers
on the wrapper `<div>`, so typed text actually triggers debounced
server-side project search. Projects are now ordered newest-first
(`Query.orderDesc('')`) matching the org projects page.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The project selector previously loaded up to 100 projects upfront,
silently truncating orgs with more. Replace InputSelect with
Input.ComboBox per row — each row fetches the first 25 projects on
open, then debounces API search as the user types. Already-selected
projects are filtered from each row's results. Removes the orgProjects
prefetch from createMember entirely; edit modal no longer needs a
projects prop.
- Skip the extra listMemberships call for org owners (they always see
all projects), avoiding unnecessary latency on every org page load
- Simplify projectScopeQueries type from ReturnType<typeof Query.equal>[]
to string[]
- Track hasFetchedProjects in createMember to prevent Svelte 5 reactivity
from re-triggering the listProjects fetch when the org has zero projects
(assigning a new [] reference would re-run the effect indefinitely)
Apply BODY_TOOLTIP_MAX_WIDTH and BODY_TOOLTIP_WRAPPER_STYLE to the
+N project roles tooltip so content stays inside bounds and long
project names wrap instead of overflowing.
Members with multiple project-specific roles show the first role badge
inline. Hovering the '+N' badge reveals a tooltip listing all remaining
project roles, keeping the table clean while preserving discoverability.
Instead of wrapping all project-specific role badges in the members
table cell, show only the first badge and a compact '+N' badge for
the remainder — matching the overflow pattern used in tables DB.
When listProjects fails, orgProjects is null. The optional chain on
.projects alone leaves .find() called on undefined, crashing the
table render. Add ?. before .find() to handle this safely.
getScopes without projectId collapses project-specific roles to
org-level scopes, so we query the membership directly to get the real
roles (e.g. project-{id}-developer). Members with project-specific
roles only see the projects they have explicit access to.
Members with project-specific roles (project-{id}-{role}) now only see
the projects they have explicit access to on the org overview page.
Org-level roles (owner, developer, etc.) continue to see all projects.
listProjects already returns only the projects a user has access to
based on their org/project roles. Client-side filtering was wrong.
Keep graceful degradation on listPlatforms so a flaky call can't
crash the entire org page.
Fetch all projects in one call (up to 100) before filtering, then
paginate client-side. This ensures the total count and page numbers
reflect only the projects the user can actually access, avoiding the
broken pagination that occurred when filtering server-paginated results.
Restore graceful "No apps" degradation on org overview so pagination
stays correct. Add a try/catch around project.get() in the project
layout so clicking an inaccessible project card redirects back to the
org page rather than rendering a 404 error screen.
Projects whose platform API call fails (inaccessible/deleted on backend)
are now filtered out of the org projects list instead of shown as broken
cards. Also corrects the displayed total to match the visible count.
If any project's platform API call fails (e.g. project deleted or
inaccessible), fall back to empty platforms instead of crashing the
entire org page with a 404.
- listProjects in members page load now uses .catch(() => null) so a
permissions or network error does not break the entire page
- projectAccessSelector disables "Add project" when projects list is
empty (prevents unusable required dropdown row)
- createMember resets orgProjects on modal close so subsequent opens
fetch a fresh list instead of showing a stale one
- edit modal init effect now checks supportsProjectRoles before setting
accessType to 'specific', preventing a silent role re-submission when
the org plan is downgraded below the project-roles threshold
Add support for assigning org members to specific projects with
per-project roles (owner, developer, editor, analyst) rather than
granting access to all projects in the organization.
- Add project-role helpers to billing store: isProjectSpecificRole,
parseProjectRole, buildProjectRole, getRoleLabel, projectRoles
- Pass projectId to getScopes in project layout so project-specific
members receive correct scopes inside their assigned projects
- Add owner-only guard on org settings page
- Load orgProjects alongside members in the members page load
- New projectAccessSelector component for inline project+role rows
with duplicate-project prevention
- Invite and edit modals use radio toggle (All / Specific projects)
with inline selector — gated to plans with supportsOrganizationRoles
- Members table shows per-project role badges for project-specific members
- roles.svelte accepts isProjectSpecific prop for contextual role descriptions
Extends the same fix to three non-settings flows that call functions.update
or sites.update and had the same bugs:
- functions/(modals)/createGit.svelte: enabled/logging || → ??; add deploymentRetention
- functions/domains/add-domain/+page.svelte: enabled/logging || → ??; add
providerSilentMode, providerRootDirectory, buildSpecification, and deploymentRetention
which were missing entirely from this connect() call
- sites/deployments/createGitDeploymentModal.svelte: enabled/logging/
providerSilentMode || → ??; add deploymentRetention