[pull] main from Stirling-Tools:main - #325
Merged
Merged
Conversation
…7729) # Description of Changes ## The problem The portal's query client was created per mount: ```ts const [queryClient] = useState(createPortalQueryClient); ``` The portal is a route (`/processor/*`, a lazy element), and the switch to the editor is a client-side `navigate()`. So leaving the processor unmounts `PortalApp`, the client goes with the component, and the cache goes with the client. Coming back refetches everything, whether or not anything changed: four requests for the Users page alone (roster, grants, teams, auth config), and 21 `useQuery` sites across the portal. The editor's client sits above the router in `AppProviders` and survives the same trip. The round trip only ever cost in one direction. ## The fix The module already kept the instance in a module-level slot so `tryGetPortalQueryClient()` could find it. It just replaced it on every mount instead of reusing it, so the change is to create it lazily and hand out the same one: ```ts export function getPortalQueryClient(): QueryClient { current ??= new QueryClient({ defaultOptions: { queries: baseQueryOptions } }); return current; } ``` Still a separate instance from the editor's. The two namespace their keys apart (`["portal", ...]` against `["editor", ...]`) and invalidate independently, which this does not change. ## What this does not do `gcTime` is 5 minutes, from the shared `baseQueryOptions`. An entry with no observer is still collected on that timer, so this warms a quick trip to the editor and back, not a return after a long editing session. Raising the portal's `gcTime` is a separate decision and is not made here. ## Why it is safe **Signing out.** A cache that outlives a mount must not outlive a session, because the portal's holds the admin roster, emails and roles. Logout goes through `window.location.assign`, a full page load, so the whole JS context is discarded and no cache can survive it. Nothing in the codebase calls `queryClient.clear()` on sign-out, and nothing needs to. If logout ever becomes a client-side navigation, this needs an explicit reset, and `resetPortalQueryClient()` is the hook for it. **The one caller of the null check.** `resolveTeam` in `saas/portal/usersBackend.ts` uses `tryGetPortalQueryClient()` and falls back to a direct fetch when there is no client, which its comment describes as the unit-test path; the cache path is preferred because it honours both `staleTime` and invalidation. A longer-lived client means the preferred path is taken more often, not less. ## Testing Three tests in `queryClient.test.tsx`, and the first two fail if the client goes back to being created per call: | | | |---|---| | A remount is served from cache rather than refetching | the behaviour this changes | | Every caller gets the same instance | the mechanism | | No client is reported until the portal first mounts | the contract `resolveTeam` reads | The three existing portal caching suites called the factory expecting a fresh client per case. They now call `resetPortalQueryClient()` in a `beforeEach`, which is what keeps `sharing.test.tsx`'s "a later screen refetches nothing" case honest rather than passing on a leaked cache. `task frontend:check` passes typecheck, lint and oxfmt, and 2402 of 2404 editor tests. The two failures, `workbenchSession.test.ts` and `notificationActions.test.tsx`, are untouched here and fail the same way on `main`.
…rive it (#7415) Replaces the bare account-link login box with a guided Connect flow, and wires up the triggers that actually put it in front of someone. ## Top bar <img width="1580" height="422" alt="image" src="https://github.com/user-attachments/assets/719e12fc-121a-4caa-bc72-124c5167b011" /> ## The modal Three steps on the portal's own `FlowModal` + `StepModalHeader`, the shells procurement and prepay already wear: 1. **What you unlock** — six benefits as a plain list. <img width="817" height="503" alt="image" src="https://github.com/user-attachments/assets/4644ddd2-6181-44e1-9be9-7a961972195d" /> 2. **Sign in** — the existing `SupabaseLoginForm`, reseated. <img width="880" height="930" alt="image" src="https://github.com/user-attachments/assets/fc66cbbb-9f98-40a4-9daa-4f2447713f39" /> 3. **Connected** — confirms, then deep links into Users, Pipelines and Policies. <img width="876" height="752" alt="image" src="https://github.com/user-attachments/assets/28358e4d-a44f-4118-a8ae-8275984ebd00" /> Re-auth stays a single step with no pitch and no success screen. ## The triggers **`LinkGate` stops being dead code.** It was built as the drop-anywhere "link to unlock" wrapper and was imported by nothing. It is now a blocking empty state that replaces the feature it guards, wired into Pipelines, Policies, Users, Sources and Integrations. **Scoped to creating and editing, never viewing.** Existing pipelines, policies, sources and connections keep listing and running, so upgrading an unlinked instance cannot take away something that already works. The clicks that would open a builder or a create modal ask for the connection first, which is the moment an admin has already declared intent. ## Capability signal `accountLinkAvailable` on `/api/v1/config/app-config`. Gating needs two facts: whether the instance is linked (`LinkContext`) and whether it *could* be (this flag). The account-link endpoints 404 when the feature flag is off, which the client cannot distinguish from "not linked yet" — so gating on link state alone would lock all five views on every default install with no way out. `useConnectGate` holds that decision in one place and shares the app-config query key, so it costs no extra request. Read from the environment rather than `AccountLinkProperties` because `:core` cannot depend on `:proprietary`.
…o link (#7728) Add CI steps to enable PR deploy servers to link to prod saas. This will allow pr testing of payment flows, usage of real credits etc
Redesigns the policies system so that the backend has an understanding of policies running over the Editor. The Editor is not set up as a source for the backend because the backend can't actively get files from it, they come in via the frontend sending them to the backend, so instead pipelines have a specific editor key in them to encode whether the pipeline is triggered on file upload/export in the editor. Also make a big effort in the frontend code towards genericising policy running. Previously, there was specific support in the main policy executor for each policy that it had to run, which was not going to be appropriate long-term, especially when users can run any pipeline in the editor. There's more work needed here for me to really be happy with it but this PR is plenty large on its own and moves it in the right direction. All of the above was required to allow arbitrary user pipelines to run in the editor. This PR makes it so that the user can select Editor as a source in the pipeline creator, along with whether it should run on upload or export. <img width="1437" height="506" alt="image" src="https://github.com/user-attachments/assets/b2d176a1-185c-480b-9916-abdd1447d8e1" /> --------- Co-authored-by: James Brunton <james@stirlingpdf.com>
…hem (Review Flow PR 5a) (#7761) Review Flow PR 5a — the first half of #7479, which stays open for reference until both halves land. This PR is the ranking and the bookkeeping; #7762 adds the retry handlers. Merging both reproduces #7479's diff byte-for-byte. ## What's added **The action slot model (backend).** `FailureActionSlot` ranks each of a kind's offers as its `RESOLUTION`, `SECONDARY` or `OVERFLOW`. `FailureKind` now declares placement per offer — the password-protected kind names `DECRYPT_AND_RETRY` as its resolution, `UNKNOWN` leads with a plain `RETRY` — and `FailureActionId` gains those two ids. The declarations are data; their client handlers arrive in the follow-up, so this build withholds them with a reason rather than rendering unwired buttons (the same forward-compatibility #7478 relied on). **A resolve transition.** `POST /api/v1/notifications/{id}/resolved` lets a client report a failure fixed. `NotificationSource.parse` turns a qualified notification id back into the source that owns it, and `FileRunEventService` folds the resolution into the incident rather than deleting it. **`viewerReviewsTeam` on the list response.** A member sees only rows whose document this browser holds — they can neither open nor fix anything else — while a team reviewer keeps every row. **The bell renders the ranking** (`promoteActions`): one primary button, at most one secondary, the rest in an overflow menu beside **Copy log**. The row's body is the kind's own sentence; the raw failure message moves into the menu. **Read state is a timestamp, not a row id.** `readThroughAt` replaces `lastSeenId`: when a resolved or dismissed row leaves the list, the rows below it stay read instead of re-lighting the badge. ## How to test Needs a proprietary or SaaS build with login enabled (`task dev:all`, sign in). 1. **Create a failure.** Add a password-protected PDF to the editor and choose **Skip for now**; the upload's policy run fails on it. 2. **Open the bell.** The row reads the kind's sentence, not a stack trace. Its primary button is **View file** — the server offers Decrypt and retry as the resolution, but this build withholds it (handler lands in the follow-up), so the best renderable offer is promoted instead. 3. **Open the row's ⋯ menu.** View in processor and Dismiss sit there, along with **Copy log**, which copies the raw message. 4. **Check the read marker survives a departure.** With two failures, open the bell (badge clears), dismiss the newer row, and refresh: the badge stays dark. On main, the marker held the departed row's id and the older row re-read as unread. 5. **Member visibility.** As a plain member, a failure recorded from another browser does not appear in the bell; as a team reviewer it does. 6. **Resolve endpoint.** `POST /api/v1/notifications/failure-{eventId}/resolved` as the owner removes the row on the next poll; `NotificationResolveTest` pins refusal for a non-owner, an unknown id, and a foreign prefix. ## Migration None.
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )