fix(frontend): @agenta/sessions-ui exports the components its consumers import - #5918
fix(frontend): @agenta/sessions-ui exports the components its consumers import#5918ardaerzin wants to merge 80 commits into
Conversation
…token source for oss, ee and mobile
…d transform leave the app
…d file surfaces leave the app
…ation-ui — one shell for desktop and mobile
…ove into @agenta/playground-ui
… into @agenta/sessions-ui
…ent roster leave the app
This lane reintroduces `first_agent_intent` call sites that the same fix on `pkg/session-surfaces` couldn't see, so they still send the raw "describe your agent" text under a `message` property. `truncateForCapture` only capped length — a composer message can hold credentials, customer data or a pasted secret, and none of that belongs in an analytics property. `classifyAgentIntent` is the signal the module says it exists to capture: it buckets the message into support/research/ops/content/coding/data/other. The two free-text submit paths already sent that bucket alongside the raw text, so dropping the text loses nothing there; both coding-agent-copy paths (the home strip and the onboarding chat composer) gain the classification they never had. `truncateForCapture` and `MESSAGE_CAPTURE_LIMIT` go with their last reference.
…bounce behind AGENTA_MOBILE_REVERSE_GATE
The tabbed workspace decides live-vs-replay on entityId alone, which is null while the agent query is still pending — so an agent-backed session rendered the replay branch and its "no agent to message yet" notice until resolution landed. Hold the chat slot on ChatLoading until useAgentEntity settles.
…joins the components in @agenta/entity-ui Every component on the agent overview was already shared; only the two-column arrangement was still written twice, inline in the desktop page and again in the mobile screen. AgentOverviewLayout takes main/rail slots and owns the geometry, so /m stops rendering a single 768px column at desktop widths and the two surfaces cannot drift. `scroll` distinguishes the hosts: the desktop page is a full-height frame whose columns scroll independently, while mobile's ScreenScaffold already owns the scroller.
…st the frame The components on this page were already shared; the ARRANGEMENT was not, so /m assembled its own sections in its own order and was missing Files and Usage entirely. AgentOverviewBody is the whole body — composer slot, both list cards, and the rail in one order (what the agent IS, CARRIES, WILL DO, COST) — rendered by the desktop page and the mobile screen alike. SessionListCard moves to @agenta/sessions-ui with the three app verbs as props (mobile's useSessionRowMenu already returns exactly those), replacing mobile's own AgentOverviewSection. AgentFilesCard moves to @agenta/entity-ui/agent, de-antd'd so mobile can render it. The OSS wrappers those two replaced are gone.
/m already had a start-session composer on Home; the agent page had none. Rather than a second composer, HomeTaskComposer gains fixedAgentId — with it the picker drops, because an agent's own page already answers which agent and a picker there would let you send from it to a different one. AgentComposer is the mobile binding, on HomeComposer's mint-stash-route flow so attachments staged before the session exists keep their scope.
…s its margins ContentRail renders a plain div, so the body's `flex-1`/`h-full` columns had no definite height to resolve against and the left one scrolled inside a stunted box. Making the wrapper a flex column restores the chain. Padding scales with the viewport — tight on a phone, desktop-like margins from lg.
…sktop margins UsageCard already serves /m's home and takes an appId, so the agent rail scopes it to this agent — the same figures the desktop strip shows. Margins go from 24px to 64px at lg, measured against the desktop page rather than guessed.
The kebab lived in the OSS page: an antd Dropdown wired to app-management modals, with /m offering nothing. AgentActionsMenu is the shared verb set — rename, copy id, copy slug, delete — on Radix, and useAgentActions is its default implementation (updateWorkflow / archiveWorkflow, both already in entities), built the way useSessionActions already does shared verbs. Rename and delete take host overrides so the desktop keeps its app-management modals, which validate and refresh the apps cache; /m has none and falls through to the shared ones. Copying is shared outright. The swap left the OSS header's antd Dropdown, Button, MoreOutlined and copyToClipboard imports dead; they're gone.
The title carried flex-1, so it absorbed the row and pushed the menu to the opposite end. Sizing it to its text puts the two together as on the desktop; min-w-0 keeps a long name truncating.
… shared ones Both were desktop-only. SidebarToggleButton moves to @agenta/navigation-ui — it needed nothing OSS-specific once sidebarCollapsedAtom was in @agenta/navigation and EnhancedButton was antd-free — and takes collapsedAtom as a prop so it does not quietly bypass SidebarShell's own contract. The OSS path stays a re-export because settingsScope imports it too. The version rides buildHelpDocsNavItem's suffix, lazy-loaded from mobile's own package.json.
Help & Docs is a group inside a `vertical` bottom section, so NavMenu draws it through the flyout trigger, not RowLabel — where the suffix had been ported. The label was therefore missing on BOTH the desktop rail and /m. Drawn on both paths now; the collapsed icon rail still omits it, as 111 intended.
… antd items useSessionCardVerbs handed actions.menuItems() straight to SessionListCard's menuFor, which is typed (vm) => SessionMenuEntry[]. antd's MenuProps["items"] admits null entries, groups and submenus, so the shapes do not line up. toSessionMenuEntries is the existing converter the sessions page already used.
… spine leaves the app The tab model, the static-table helper and the page shell were already pure: no antd, no @/oss. They move verbatim (git mv, so 111's featureFlags->preferences rename keeps its history) and OSS keeps its three import paths as re-exports, so none of the 13 tab files move. Access is injected rather than read: useSettingsAccess needs edition detection, project permissions and org state, none of which exist in a package. The package owns the SettingsAccess shape and the visibility rules; each host computes the flags. A host with no provider gets CLOSED_SETTINGS_ACCESS, so a missing provider hides gated tabs instead of exposing them.
Both were presentation over host state, so both extract cleanly once de-antd'd: Typography -> tokens, Switch -> Radix, Radio -> RadioGroup, Divider -> a rule, danger Button -> variant=destructive, --ant-color-error* -> colorError* tokens. Theme is shared because it is a per-viewer choice both apps honour; the experiment flags stay host-supplied, since a flag only exists where it ships. AccountPage keeps the typed-email gate but delegates the dialog through renderConfirm, so no host can ship a one-click account delete.
First settings extraction with a data half rather than presentation only. The service was 46 lines over axios + the project id, both already in @agenta/shared/api, so it moves with an import swap. OSS re-exports it, keeping one implementation for its three consumers — one of which is in EE, so this is also the first settings piece to cross editions. The APIKeys page itself is untouched; splitting its fetch/permissions/dialogs into a headless hook plus a view is the next unit.
/m's settings was a placeholder saying to use the desktop. It now renders the SHARED page shell and Preferences tab, with the title and description read from the shared tab model so the copy cannot drift from the desktop's. useThemeMode extracts the half of the desktop's ThemeContextProvider both apps need — the stored preference, the theme it resolves to, and the .dark class — keyed off the same storage entry, so a viewer's choice follows them between surfaces. The antd ConfigProvider half stays in the app. Preferences only, deliberately: it is the one tab needing no profile, org or permission state. Access stays CLOSED so nothing edition-gated can leak in before /m can compute real flags.
…ccount tab The signed-in user had no package source, so /m could not render Account. fetchProfile was a three-line GET and User already lived in @agenta/shared/types, so both move into entities with a useProfile hook beside them; OSS re-exports the fetcher, keeping one implementation. The hook stays thinner than the desktop's profile atom, which also persists to disk, gates a fanout and redirects — app concerns, not entity ones. AccountPage's deletion becomes optional as a pair (action + dialog): deleting an account is an EE capability that tears down owned orgs, and /m has no EE surface, so it renders identity only instead of a button that cannot work.
…troller Two implementations of read-agenta-theme / resolve / toggle-.dark existed once /m gained its own. The state half now comes from @agenta/ui/theme; what stays is the part only this app has — antd's ConfigProvider, the darkAlgorithm token config and the agenta cssVar key class. Public API is unchanged (ThemeMode, ThemeContext, useAppTheme, getDeviceTheme), so no consumer moves. useLocalStorage, useLazyEffect, useState and the local getAppTheme fell out with the state and are gone. Behaviour note: appTheme was seeded synchronously from storage at first render; the hook reads after mount instead, because reading localStorage during render is a hydration mismatch. The document boot script has already applied the class, so there is no flash.
The tab interleaved fetching, permissions, two dialogs and the table in one component. useApiKeys owns the list and the verbs; the parts that are genuinely the host's arrive as callbacks — confirmDelete resolves from its own dialog, and onCreated receives the secret, because a key is returned once and the host has to reveal and offer to copy it there and then. ApiKeysPage is the view, off antd (Alert/Button/Tooltip onto @agenta/ui). The Loading enum in assets/constants.ts went with the old multi-flag loading state: the hook returns listing/creating/deleting.
…gs-ui, off antd Splits the EE Billing page into views and host. @agenta/settings-ui gains BillingPage (the plan card, the quota grid, members, the portal entry), PricingPlans and CancelSubscriptionReasons — the bodies of the two dialogs — plus UsageProgressBar and structural billing types of its own, so nothing in the package reaches for the OSS service layer. Each verb is a prop, and the control for a verb the host does not pass hides itself: a read-only surface reports the plan and the usage and offers no way to change either. EE keeps everything that talks to Stripe: the subscription and usage queries, the checkout / switch / cancel calls, the portal, the return-from-Stripe and ?upgrade=true round-trips, and the two EnhancedModal shells. The one read a host with no billing layer needs — /billing/usage — moves into the package. antd's Spin, Typography, Button, Card, Radio, Input and Space become Spinner, plain elements, @agenta/ui Button, a bordered panel, RadioGroup and Input; @ant-design/icons' WarningFilled becomes a filled phosphor Warning. The raw --ag-c-* hex tokens the page carried become semantic ones, so it now renders correctly in dark mode.
Access & Security crashed on /m — the mobile app has no global TooltipProvider, and this was the one tooltip in the package not carrying its own. Every other section already wraps one.
Adds useEntitlements: the access-controls catalog from /access/plans, keyed by the plan slug on /billing/subscription, giving the same six flags the desktop gates on. For a host with no entitlement layer of its own — the mobile app was rendering Access Controls, Verified Domains, SSO and the Audit Log unconditionally, so a plan that includes none of them still showed every control. The desktop keeps its own jotai version: those atoms are also gated behind its idle-boot pass and share the subscription query with the billing UI, neither of which a second caller should inherit.
Gating each of Access Controls, Verified Domains and SSO Providers separately meant a plan that includes none of them rendered three identical lock cards down the page, each saying the same thing. AccessUpgradeNotice takes whichever features are locked and says it once, naming them — and titles itself for the whole page when all three are out.
Two bugs on the same card. The plan name came from a fixed slug segment
(plan.split("_")[2]), which is empty for any slug that is not namespaced three deep —
and slugs are env-overridable, so plenty are not. It now takes the last segment, which
reads both shapes, and falls back to an em-dash rather than nothing.
The renewal line ran dayjs.unix() on a period_end the backend leaves unset for plans
that never renew, printing "Auto renews on Invalid Date". It now renders only against
a boundary that actually parses, and the type says period_end is optional because it is.
The card itself no longer requires billing to be switched on: a host that can only read
a subscription (the mobile app) still names the plan its quotas belong to.
…ta/shared processEnv is the list of keys Next inlines at build time; the billing flag was missing from it, so any package-layer read resolved only through the container's __env.js and came back empty everywhere else.
…lling service useBillingSubscription is now the single reader, shared by the entitlement gates and by whoever names the plan, so the two cannot drift apart or race for the same cache entry. It carries the desktop's retry policy, which was missing here: never retry a 4xx, and never retry 502/503/504. A billing service that is down answers every request that way, and the default three attempts just queue slow failures behind render-critical traffic.
…genta/settings-ui The package could read usage and the subscription but not act on either, so a host with no billing layer could only report a plan, never change one. Adds the four writes behind the page — switch, cancel, Stripe checkout, Stripe portal — and the two catalog reads. useBillingCatalog derives the pair the page cannot guess: which slug is the free tier (the pricing map says, and slugs are env-overridable) and whether the current plan is contact-sales. Without them the chooser offers to "upgrade" to the free plan and sends a downgrade through checkout instead of cancellation.
Triggers is missing from EE settings because it is gated on NEXT_PUBLIC_AGENTA_TOOLS_ENABLED, which is declared nowhere in hosting or CI — so it is undefined everywhere and both Tools and Triggers stay hidden. Nothing regressed; the flag has simply never been set. (One flag drives both tabs, which is worth revisiting: Triggers has little to do with the tool catalog.) /m had these hardcoded true, so it showed both tabs while the desktop hid them — the opposite of aligning the two surfaces. It also tested `license === "ee"`, missing the `cloud*` tiers the desktop's isEE() accepts. The four env-only gates move to @agenta/shared/api, which both hosts already import, and oss/lib/helpers/isEE.ts keeps only isEmailAuthEnabled — that one reads resolved auth config, not a bare env var. 15 call sites repointed. Mobile's build-time env allowlist gains the license, tools and billing keys. Without them a built image reads "" no matter how the container is configured, because `process.env[key]` with a computed key is not inlined. Gates: lint 24/24, tsc 0 across shared, oss, ee and mobile.
The previous commit claimed this and did not do it. The settings feature had been refactored in the meantime — the access object moved to settingsTabs.ts — so the string replace matched nothing and no-op'd silently, and lint/tsc stayed green because nothing was broken, only unchanged. canShowTools/canShowTriggers now come from isToolsEnabled(), and isEE from the shared isEE() rather than a bare `=== "ee"` that misses the cloud tiers. settingsTabs.ts is the only place mobile builds SettingsAccess, so this is the whole surface.
readOnly was a guard against the create/edit drawers rendering antd forms with no ConfigProvider here. That reason is gone — SchemaForm and SubscriptionForm moved to @rc-component/form, so the drawers carry no antd theming. Destructive actions in those sections ask for confirmation through an imperative callback (the desktop passes antd's AlertPopup). useConfirmSheet adapts the existing ConfirmSheet, which is controlled, to that shape, and reports a failed action in the sheet rather than swallowing it — this app has no toasts. Subscriptions and schedules take no confirm: they delete through their own handlers, not through the shared prompt. Gates: lint 24/24, tsc 0 on mobile. Not yet exercised in a browser — the drawers and the confirm path are the things to try.
A section with a title but no search rendered the heading, then the buttons on their own row below — a band of empty space between them, and the buttons sitting closer to the table than to the section they act on. The three Triggers sections all read that way. The actions now ride the title's row when there is one, and fall back to the end of the filter row when there is not (Tools, which searches and has no title, is unchanged). A section with both gets the actions beside the title and the search on its own row, since the search box is too wide to share. Aligned to the top of the title block, which is two lines — name over description. Gates: lint 24/24, tsc 0 across ui, settings-ui, oss and mobile.
Four narrow-viewport fixes, all in the primitive so every settings tab gets them: The header row stacks below sm instead of wrapping. `ml-auto` meant the buttons landed hard-right once they wrapped, leaving a gap under the description — they now span the row. The empty state's call to action is the only one shown on a phone. The toolbar carries the same button, and at this width the two sit far enough apart to read as different controls. Checked every caller first: the CTA-less empty states are all search-no-match ones, where the search box is right there, so nothing is left without an action. Search goes full-width below sm; a fixed 260px overflowed. The empty region loses some height on narrow — three stacked sections were mostly whitespace before reaching content. Not fixed here: the table still compresses rather than scrolling, because it is `w-full` with no min-width. Five columns in 360px is the real problem and wants a card-per-row layout below sm — its own change. Gates: lint 24/24, tsc 0 across ui, settings-ui, oss and mobile.
A full row for one icon button is too much. Reload now stays beside the section title at every width; only the primary button — wide, labelled — wraps to its own full-width row below sm, and rejoins the line from sm up. With the empty state hiding the primary button on a phone, the header collapses to a single line: title, description, reload. Only this file is staged — the other changes in the tree are another agent's work in flight (mobile's tsc is failing on jotai-family resolution and some nav/chat edits that have nothing to do with this).
…entity-ui OSSdrillInUIProvider carried 588 lines of workflow-reference wiring that was not OSS-specific — /m needs the same behaviour to render a config pane, and could not reach it. It becomes useWorkflowReferenceBridge in the package, and the provider keeps only the host's own composition. The bridge imports nothing from @/oss, so it holds in a host that cannot see the app layer. Lane: this belongs with the entity-ui work, above pkg/session-surfaces — see docs/design/sessions-ux-stack/restack-onto-112.md.
Consumes useWorkflowReferenceBridge through a 20-line provider, so /m gets the reference behaviour the desktop has instead of a second implementation. Lane: mobile, and it depends on the bridge commit below it.
…eclare it Six packages imported `atomFamily` from `jotai/utils` while resolving it through a transitive copy — @agenta/mobile was failing tsc on `Cannot find module 'jotai-family'` because nothing in its graph declared the dep. Every call site now imports from `jotai-family`, and each package that uses it lists it. 46 files, one behavioural change: none. Lane: this is package-wide plumbing and belongs below the settings lanes — see docs/design/sessions-ux-stack/restack-onto-112.md.
The title rules — normalize, truncate on a character boundary, join the parts — lived in oss/components/PageTitle/utils and /m had its own `parts` array doing roughly the same thing. They now share one implementation, with its test. The surrogate-pair guard is the reason this is worth sharing rather than reimplementing: a server-persisted session title can arrive cut mid-pair, and the trailing high surrogate renders as a replacement character. Each host's PageTitle is now just the <title> element for its router. Lane: @agenta/shared, below everything else; the oss and mobile rewires ride with it since the old util is deleted in the same change.
The SDK guards those imports with try/catch, so webpack resolving them is noise. Production only — dev runs Turbopack.
…rs import
Six components existed in the package but were never re-exported from the barrel, so
every import of them failed. `/m`'s Home route died on a hard build error ("Export
SessionListPanel doesn't exist in target module"), and `tsc --noEmit` reported 21
errors across oss, ee and mobile — all one signature.
Adds SessionListPanel, SessionTab, SessionTabDragItem, SessionTabStrip, SessionTabRail
and useSessionActions (with SessionActionTarget, SessionLocalCache and their prop
types) to src/index.ts. No component code changes — only the barrel.
tsc: oss 17 -> 0, ee 17 -> 0, mobile 4 -> 0.
Pre-existing on the base and the only eslint ERROR in web/mobile, so mobile's lint task failed for anything branching off it. Applied with eslint --fix; formatting only.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe PR adds public exports for session UI components and session actions types. It also reformats existing ChangesSession UI public exports
Chat hook formatting
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
6fdcb88 to
15c246b
Compare
Six components live in
@agenta/sessions-uibut were never re-exported fromsrc/index.ts, so every import of them failed. The components themselves are fine — only the barrel was missing lines.This is not a latent type-only problem.
/m's Home route is currently a hard build error:and
tsc --noEmitreports 21 errors across oss, ee and mobile, every one of them the same signature (Module '"@agenta/sessions-ui"' has no exported member …).The change
src/index.tsgains the exports its consumers already import. No component code is touched.SessionListPanelSessionListPanel.tsxagenta-home-ui/HomeOverview.tsxSessionTab,SessionTabDragItem,SessionTabStriposs/…/AgentChatSlice/components/SessionTagBar.tsxSessionTabRailSessionTabRail.tsxmobile/…/features/chat/SessionTabs.tsxuseSessionActions,SessionActionTarget,SessionLocalCacheuseSessionActions.tsxoss/…/hooks/useSessionActions.tsx,mobile/…/useSessionRowMenu.tsThe second commit is unrelated but tiny: a prettier error in
mobile/src/features/chat/ChatScreen.tsx, pre-existing on the base and the only eslint error inweb/mobile, so mobile's lint task fails for anything branching off this base. Applied witheslint --fix; formatting only. Split out so it can be dropped independently.Verification
tsc --noEmit: oss 17 → 0, ee 17 → 0, mobile 4 → 0;@agenta/sessions-uiand@agenta/home-uialso 0.Lint 23/23 green, and
web/mobilegoes from 1 error to 0 (4 pre-existingreact-hooks/exhaustive-depswarnings remain, untouched).Driven in a browser on
/m, since a build error is what this actually fixes — each route below rendered with no export error in the console:/apps) — the route that was 500ing. Renders the Sessions list (SessionListPanel), agents, Next triggers, Usage./sessions) — filters and list render; the row menu goes throughuseSessionActions./sessions/<id>) — the session tab rail renders with its tabs, exercisingSessionTabRail/SessionTabStrip/SessionTab.Note for anyone reproducing: Turbopack cached the stale barrel and kept reporting the missing export after the file was correct. The dev container needs a restart, not just a recompile.