[release] v0.100.5#4473
Merged
Merged
Conversation
Sticky and maxWidth columns now honor user-resized widths and the resize handle is wired uniformly across fixed, constrained, and flexible columns. Column-group headers (e.g. Evaluators) become resizable; the drag delta distributes proportionally across leaf children so the group expands uniformly instead of one column absorbing the entire change. handleResize commits the new width on every drag frame so AntD's colgroup updates live — header and body resize together instead of snapping on release. ResizableTitle no longer carries an inline width when idle, so AntD's colgroup is the sole source of truth outside of an active drag.
… anchor sticky-right resize handle inside cell Two table layout fixes: - After fast horizontal scrolling, AntD's body cell widths can drift away from the header colgroup until something forces a column-level re-render. A debounced scroll listener on .ant-table-body bumps a layoutNudge counter 150ms after horizontal scroll settles; the memoed finalColumns then produces fresh column object references and AntD rebuilds its layout state. The listener ignores vertical scroll so normal browsing has no extra re-render cost. - The resize handle on sticky-right columns (e.g. the observability Actions column) is anchored inside the cell (right: 0; width: 9px) instead of overhanging by 9px, removing the ~18px ghost slot that used to appear on the table's right edge.
NodeNameCell renders the full span name through Typography.Text with
ellipsis={{tooltip: name}} so truncation tracks the rendered column
width and the full name is reachable via tooltip. The
nodeDisplayNameAtomFamily that hardcoded a 15-character JS slice is
removed.
Prefer externalEntity over currentEntity when computing createFieldsEntity to avoid stale atom data from previous sessions where afterClose reset never fired (e.g. parent unmounted mid-animation). This ensures Effect 2's default-name computation uses fresh data on first open.
The 'Configure' and 'View details' items in the automatic evaluator row menu both called handleConfigure, opening the same drawer. The 'View details' entry was a stub introduced with the new evaluators table that never got its own handler — remove it.
A column group can't shrink smaller than every child being at its own minimum simultaneously, so the parent floor must be the sum of child minimums, not the smallest. No behavioural change today because table-layout: fixed ignores the parent th's minWidth — this is a hygiene fix that becomes load-bearing the moment we add minConstraints to the Resizable handle.
…nal targeting flag The "Signup 2" survey is type=api with schedule=once, which makes PostHog auto-generate an internal_targeting_flag_key (invisible in the dashboard). posthog.getActiveMatchingSurveys runs that flag through the eligibility filter, and for brand-new identified users it evaluates to false before the flag context settles — so the survey is silently filtered out and useSurvey rejects with survey-unavailable. Switch the fetcher to posthog.getSurveys, which returns the raw survey list without the eligibility filter. We filter client-side by name + start/end date. The "show once" decision is owned by our own backend on form submit anyway, so bypassing the SDK-side gate is correct here. Also widen the auto-redirect list in PostSignupForm so transient errors (posthog-unavailable from the load timeout, survey-fetch-error from the new fetch timeout) fall through to /get-started instead of leaving the user on a blank page. We lose survey data on those paths, not signups.
…consumer
Restructures /post-signup to remove the race-condition workarounds that had
accumulated in PostSignupForm. Replaces the single ~480-line component (which
fetched its own posthog, profile, orgs, survey and defended against each being
absent) with a clear separation:
PostSignupRoute (new)
- Owns every async dependency via usePostSignupReadiness.
- Renders one of: <Skeleton>, <Form>, <Fallback>, or fires a router.replace.
- The only place that decides "are we ready to show the form?".
usePostSignupReadiness (new)
- Composes useSurvey + useProfileData + useOrgData into a discriminated
state: loading | ready | skip | fallback.
- "skip" reasons (no API key, survey deleted) → redirect to /get-started.
- "fallback" reasons (transient PostHog or fetch errors) → show a small
"we couldn't load the welcome survey, continue" UI instead of bouncing
the user silently.
PostSignupForm
- Now a pure consumer. Receives survey, user, orgs, posthog as props that
are guaranteed populated. No internal data fetching, no spinner, no
auto-redirect effect, no debug logs.
PostSignupHeader, PostSignupSkeleton, PostSignupFallback (new)
- Small presentational components extracted from the original file so the
loading and fallback states keep the brand header visible.
pages/post-signup/index.tsx
- Slimmed to a 5-line route handler. Debug console.logs removed.
The timeouts inside useSurvey are unchanged — they remain the single source
of truth for "PostHog SDK didn't load" / "survey fetch hung", and the
readiness hook just routes their resulting error codes into the right
rendering branch. Components no longer carry their own timers.
Adds three sections capturing patterns from recent FE work, primarily PR #4425 (workflow Fern migration + playground vitest setup): - "Frontend API: Use the Fern Client" — all new FE API code goes through @agenta/sdk's getAgentaSdkClient, not raw axios. Pattern, anti-patterns, migration policy, references to existing Fern-using domains. - "Code Placement: Packages vs. Application Code" — quick heuristic + hard rules, with a forward pointer to the existing agenta-package-practices skill for the full ruleset. - "Package Unit Tests" — layout (tests/unit/, not src/), minimal vitest config, scripts, Fern-client mocking pattern, what to test / skip. References commit 1c0a900 (exclude in-src tests from package build). Dev Environment Tips section now points to the three new sections so they're discoverable from the top of the file. Source: Slack thread between Mahmoud and Arda, 2026-05-27.
…mpty-form flash Previously, clicking Submit called form.resetFields() before router.push() and the user saw a blank form for the ~200-500ms it took Next.js to navigate to /get-started. Confusing for new signups — looked like the form was wiped out or had silently errored. Three small changes: - Drop the resetFields() call. The page is unmounting anyway, so resetting fields has no effect except triggering that flash. - Add an isSubmitting state that flips on at the start of handleSubmitFormData and persists through navigation (the component unmounts before any reset could matter). - Wrap the form in a Spin with "Setting up your workspace…" and put the submit button in its loading state while isSubmitting is true. The form stays visible but dimmed, so the user gets clear feedback that submission is in flight.
Four refinements to the new sections, all surfaced by /plan-eng-review:
1. Frontend API: Use the Fern Client
- Add a Prerequisite subsection: new packages adopting Fern must
declare @agenta/sdk in package.json dependencies. Without it,
tsc --noEmit fails on the import before runtime.
- Migration policy: replace the single paragraph with three explicit
bullets so the edge case "new function in existing axios-using
file" is covered (use Fern; don't migrate sibling functions).
2. Package Unit Tests
- Mocking the Fern client: add a one-paragraph note about singleton
behaviour. The mock returns a fresh object per call; tests that
depend on the production singleton need a stricter mock.
- New "Integration tests" subsection covering vitest.integration
.config.ts: separate config, raised timeouts, sequential
execution, when to choose integration over unit. References
@agenta/entities's existing integration test infra (4 files,
setup/global.ts + setup/worker.ts).
No structural changes, no anchor changes, no removed content.
…ness watchdog
Two regressions observed in local testing:
1. New signups landed directly on /get-started without seeing the survey.
Root cause: useSurvey was firing as soon as posthog.__loaded flipped true,
but posthog.surveys (the SDK extension that fetches /api/surveys/) hadn't
finished initializing yet. The top-level posthog.getSurveys at that point
synchronously calls back with [] + {isLoaded:false, error: SURVEYS_NOT_AVAILABLE},
which my code read as "survey not found" and routed to skip → redirect.
Fix: gate the getSurveys call behind posthog.onSurveysLoaded, so by the
time we query for the survey the extension has settled and /api/surveys/
has returned (or errored loudly). This is the part of JP's hotfix that
was correct — the only thing wrong with his fix was using
getActiveMatchingSurveys (eligibility-filtered) rather than getSurveys.
We now have both: wait for surveys-loaded, then read the raw list.
2. Some signups got stuck on the loading skeleton indefinitely. The
readiness hook stayed in `loading` whenever any of (surveyLoading,
profileLoading, posthog, user, survey) was falsy, so a stalled profile
query or a null user-after-profile-loaded kept the spinner alive forever.
Fix: add a 10-second watchdog. If the gate is still in `loading` after
that, it transitions to `fallback` (reason: watchdog-timeout) and
PostSignupFallback shows the existing "we couldn't load the welcome
questionnaire, continue" UI. The 10s budget sits above useSurvey's own
6s timeouts so its specific errors still take precedence, and it's a
single backstop for anything else (profile, orgs, future dependencies).
Bonus cleanup in useSurvey: the SDK's onSurveysLoaded fires its callback
synchronously when surveys are already loaded, which used to race the
unsubscribe assignment. Guard against that — release the subscription
immediately if the promise already settled inside the call.
…prefetch /get-started The Spin overlay approach was unreliable: antd renders the spinner inside the wrapped Form, and the form briefly unmounts mid-route during Next.js's chunk fetch — so on Submit the user sometimes saw a blank page instead of the loading affordance. Two changes: 1. Replace the Spin-over-Form pattern with a full-page PostSignupSubmitting view that takes over the moment isSubmitting flips true. No overlay positioning, no half-disabled form — just "Setting up your workspace" with a spinner. The form is going to unmount anyway; rendering an unambiguous transition state in its place is clearer than trying to keep the form visible while it's being dismantled. 2. Prefetch /get-started from PostSignupRoute on mount. In dev mode Next.js compiles routes lazily and the post-submit router.push can take 1-3s while the chunk is built; in prod the prefetch is cheap and ensures the route swap is effectively instant. Pages-router prefetch is idempotent so calling it on every mount is safe.
…g navigation The transition view existed but never reached the screen. React 18's automatic batching put setIsSubmitting(true) in the same task as posthog.capture and router.push, and the prefetch we added to /get-started made the route swap fast enough that the new render committed *after* the form unmounted. The user saw the form being torn down — looking empty — right up until /get-started replaced it. Use flushSync to commit the submitting render synchronously, then await one requestAnimationFrame so the browser paints it before we begin the route change. This gives the "Setting up your workspace" view a guaranteed visible moment regardless of how fast the prefetched route swaps in.
Temporary instrumentation to figure out why the PostSignupSubmitting view
isn't reaching the screen on submit. Logs at every step:
[post-signup][diag] PostSignupRoute render (status: ...)
[post-signup][diag] PostSignupRoute MOUNTED / UNMOUNTED
[post-signup][diag] PostSignupForm render (isSubmitting: ...)
[post-signup][diag] PostSignupSubmitting render
[post-signup][diag] handleSubmitFormData start
[post-signup][diag] flushSync(setIsSubmitting=true) done
[post-signup][diag] rAF yielded
[post-signup][diag] posthog.capture('survey sent') called
[post-signup][diag] calling router.push('/get-started')
[post-signup][diag] routeChangeStart / routeChangeComplete
Will be removed once we have the trace.
Three correctness fixes raised in review:
1. useSurvey.isSurveyRunning was treating any survey with a future end_date as
not-running. Now compares both start_date and end_date against "now",
treating end_date === null as "no scheduled end".
2. Survey lookup used name.includes(surveyName), which would also match
"Signup 20" if a future survey is added with that name. Switched to
strict equality.
3. router.push('/get-started') in the submit handler is now awaited; on
rejection we reset isSubmitting so the user can retry instead of being
stuck on the "Setting up your workspace" view forever.
Also removes the temporary [post-signup][diag] console.log statements that
landed in dee7651 — they served their purpose tracking down the dev-server
location issue and shouldn't ship to prod.
Four trivial fixes from CodeRabbit's CR pass: - @agenta/sdk prerequisite: "before the code runs" → "at type-check time" (tsc --noEmit is compile-time, not runtime). - Three fenced code blocks gain `text` language identifier so markdownlint renders them cleanly: packages-vs-app decision tree, unit tests directory layout, integration tests directory layout. No content or rule changes.
Two independent bugs surfaced in #bugs as the "demo workspace banner
issue":
1. URL pollution (Mahmoud's investigation). projectAtom derived
workspaceId from selectedOrgAtom, which lags /projects/ by one
/organizations/{id} fetch. During the gap, pickPreferredProject
ran with workspaceId=null and hit the cross-org
`projects.find(p => p.is_default_project)` fallback. For users
with is_demo=true membership, that returned the Demo Workspace's
default project. WorkspaceRedirect then router.replace'd to
/p/<demo-project>, pinning the URL and rendering the banner in
the wrong workspace.
Fix: projectAtom falls back to selectedOrgIdAtom (URL-derived,
resolves on first render) so workspaceId is always populated.
projectMatchesWorkspace already accepts either workspace_id or
organization_id, so the URL-derived org UUID filters correctly.
2. Banner-missing-on-reload race. Two parallel "session exists"
atoms existed — @agenta/shared/state's sessionAtom (entity
packages) and oss/state/session's sessionExistsAtom. The eager
init in appState/atoms.ts set the shared one; the oss one
waited for SessionListener's React effect. projectsQueryAtom
gated on the oss atom AND on profileQueryAtom.data.id, which
forced /projects/ to wait for /profile/ sequentially. On cold
reload the demo banner couldn't render until ~2 RTTs +
1 effect tick later.
Fix: consolidate to a single source of truth — sessionExistsAtom
is now a re-export of @agenta/shared/state's sessionAtom. The
~14 callers keep their imports unchanged. SessionListener and
useSession drop their manual dual-write code. projectsQueryAtom
drops the redundant profile.data.id gate (fetchAllProjects uses
session cookies, not user.id). Cold-reload banner now appears
after one network RTT, with the same speedup applying to every
other query gated on sessionExistsAtom (access, observability,
selectedOrg, profile).
Includes a vitest regression spec for pickPreferredProject behavior
(workspaceId=null returns cross-org demo; workspaceId scoped to own
org returns own project). web/oss has no vitest runner yet, so
*.test.ts is added to tsconfig exclude — the spec runs once vitest
is wired.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…st-signup-survey-issues
[FE fix] Post signup survey issues with PostHog
…pdated-patterns chore(docs): document new frontend patterns in AGENTS.md
Contributor
Author
Railway Preview Environment
Updated at 2026-05-28T19:09:25.089Z |
The own-org "Default" project had is_default_project: true in the fixture, which made `projects.find(p => p.is_default_project)` (the cross-org fallback inside pickPreferredProject when workspaceId is null) return the OWN project first — not the demo one. That contradicted the "with workspaceId=null, returns demo project" assertion and meant the test wasn't locking the bug it claimed to. Mirror Mahmoud's actual /projects/ response from the investigation: own-org "Default" is_default_project=false, Demo Workspace "Default" is_default_project=true. With that ordering, the find lands on the demo project — exactly the path that pollutes the URL on cold reload.
…issues [FE fix] demo workspace banner issues
…u-top-two-actions-have-identical-effect [AGE-3773] fix(frontend): remove duplicate 'View details' evaluator menu item
|
Deployment failed with the following error: |
[Frontend / Fix]: Fix name memoization issues in entity commit modal
[Frontend Fx] Observability table column resizing issues
mmabrouk
approved these changes
May 28, 2026
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
New version v0.100.5 in