The "Tool approval needed" toast (and several sibling surfaces) could
linger after the state they represent was already gone. User-scoped SSE
events (tool.approval.required, schedule.autopaused, attachment.queued,
…) are durable and replayed on reconnect, but no terminal path emitted a
matching clearing event and the reconciler only wrote operator-facing
stack_logs — so a failed/expired message replayed its approval prompt
with nothing to act on, and the toast trusted event presence over the
actual message state.
Backend — emit a user-facing event on every terminal path:
- reconciler deletes pending_tool_state and publishes
tool.approval.cleared when a stuck message is failed;
cleanup_pending_tool_state does the same for TTL-reaped rows
- reconciler now publishes source.ingest.failed (stalled ingest),
schedule.run.failed (timeout/pending) and schedule.completed (once)
- schedules PATCH-resume / DELETE publish schedule.resumed / .cancelled
so a stale schedule.autopaused can't outvote them on replay
- store_attachment gains the on_poison terminal-event hook the ingest
tasks already have; mcp_oauth_task gains a soft/hard time limit so a
hung flow self-reports mcp.oauth.failed
- tighten the reconciler exemption to the (conversation_id, user_id)
composite key
Frontend — stop trusting event presence over truth:
- notificationsSlice.resolveToolApproval evicts the matching
tool.approval.required and persists its (stable) id dismissed so the
backlog replay stays suppressed
- ToolApprovalToast drops approval events older than the resumable TTL
window as a backstop for a lost clearing event
- schedulesSlice handles schedule.resumed / .cancelled / .completed
- upload dismissals now outlive the SSE backlog retention window
Tests cover the new clearing events at the reconciler, slice and
dispatch layers.
- test_standard_clients_can_ignore_docsgpt: docsgpt-only events now ride
on a valid empty chat.completion.chunk (choices[0].delta == {}) so
strict OpenAI clients accept the frame; assert the envelope plus the
docsgpt extension instead of the absence of choices.
- split continuation persistence into stateful (conversation_id present
-> save) and stateless (no conversation_id -> no save) cases.
- _fake_translate_stream_event mock accepts strip_reasoning_leak kwarg.
- Drop multimodal content for non-OpenAI-family providers (Google/Anthropic
raised on OpenAI image_url parts) so multimodal requests degrade to text
instead of returning 500.
- Stateless continuations (no conversation_id) default save_conversation to
False, avoiding an orphan conversation with an empty question on every tool
round (stateful continuations still default True).
- Only strip leaked reasoning from content for structured requests
(response_format / json_schema / json_object); legitimate answers that mention
the marker text are no longer corrupted.
- Forward sampling params (temperature, max_tokens, ...) on continuation turns.
- An explicit json_object request clears an agent-configured json_schema so it
isn't silently overridden.
- Drop max_tokens when max_completion_tokens is also sent (OpenAI rejects both).
- Don't build a chatcmpl-None completion id from the placeholder "None" id event.
OpenAI-compatible clients send multimodal user turns as a `content` array of
typed parts. translate_request previously assigned the array straight to the
question, breaking the string-only retrieval / token-budgeting / history paths
(HTTP 500). Now:
- content_to_text() extracts text from content arrays for the question,
history and system prompt, so the string paths work unchanged.
- The full content array (text + image_url parts) is preserved as
`multimodal_content`, threaded to the agent and emitted as the final user
message so images reach the model. Token budgeting uses the text only.
The content array (incl. image_url) now reaches the LLM call intact; images
render for vision-capable models. A text-only upstream model will reject the
image_url variant, as expected.
- Stateless tool continuation. OpenAI-compatible clients (opencode, etc.)
resend the full messages array — system, user, assistant(tool_calls),
tool(results) — but no conversation_id, so the prior
"conversation_id required for tool continuation" 400 broke every tool call.
When no conversation_id is present, rebuild the agent + pending tool calls +
tool results directly from the resent messages
(StreamProcessor.build_continuation_from_messages) instead of loading
server-side pending_tool_state, and call gen_continuation.
- Forward OpenAI sampling params (temperature, max_tokens,
max_completion_tokens, top_p, frequency_penalty, presence_penalty, stop,
seed) from the request to the LLM gen call; the agent otherwise uses its
configured defaults.
Strict OpenAI clients (e.g. opencode / the Vercel AI SDK) validate every
streaming `data:` frame as a chat.completion.chunk. The id / source /
tool_call / tool_calls_pending events were emitted as bare `{"docsgpt": ...}`
objects with no `choices`, which those clients reject ("expected array,
received undefined" on `choices`).
Wrap the extension in an otherwise-empty chunk
(choices:[{index:0,delta:{},finish_reason:null}]) with a top-level `docsgpt`
field that OpenAI clients ignore, and skip the placeholder "None"
conversation_id frame emitted when the call is not persisted.
Make the OpenAI-compatible Chat Completions endpoint honor per-request
Structured Outputs and keep it OpenAI-compatible.
- translate_request now forwards the request's `response_format` (json_schema)
or a `response_schema` convenience field to the agent as its json_schema,
overriding the agent-configured schema for that request.
- Honor `response_format.json_schema.strict` (default true); strict:false
passes the schema through without forcing additionalProperties:false /
all-required (OpenAI's lenient mode).
- Support `response_format {"type":"json_object"}` via the provider's native
JSON mode.
- Keep `content` clean: some models echo their reasoning into content as
stringified `{'type': 'thought', ...}` reprs when response_format is set.
Strip those from content and reroute them to `reasoning_content` (OpenAI
never puts reasoning in content), for both streaming and non-streaming.
Flow: translate_request -> StreamProcessor._configure_agent ->
Agent.json_schema / json_schema_strict / json_object -> _llm_gen ->
prepare_structured_output_format(schema, strict).
Live /v1/responses rejects input_text for the assistant role
(input[N].content[0] -> "Supported values are: 'output_text' and
'refusal'"). Revert the previous review-driven change: encode assistant
turns as output_text and user/system turns as input_text. The SDK type
union suggested input_* only, but the API enforces output_text per-role —
confirmed against the live endpoint.
- Replay prior-turn assistant text as input_text, not output_text: a
Responses easy-input message only accepts input_* content parts, so the
old shape would 400 on the second turn of every conversation (default
store=false resends prior turns inline).
- Always request include=["reasoning.encrypted_content"] so in-turn
reasoning carryover works whether or not the response is also stored
server-side (OPENAI_RESPONSES_STORE).
- Add detail="auto" to input_image parts.
- Remove the now-dead azure_openai option from setup.sh / setup.ps1 and
the docs, completing the AzureOpenAILLM removal.
- Tests: correct the input_text / image-detail / include assertions and
add parallel tool-call and stream-error coverage.
Reconcile the reasoning-passthrough change (#2520) with the Responses API
work: dedupe the shared reasoning_effort capability, keep upstream's
upstream_model_id YAML plumbing and the _apply_reasoning_effort chat path,
and run the Responses branch ahead of it so responses-flavor models skip
the chat-only top-level reasoning_effort injection.
Route opted-in models to /v1/responses while keeping Chat Completions as
the default. The flavor is selected per-model in the catalog YAML and
threaded through ModelCapabilities (with a new reasoning_effort field).
- Translate chat-shaped messages/tools/structured-output into the
Responses input / text.format / flattened-tool shapes at the provider
boundary; stored history and intra-turn tool round-trips need no
migration.
- Normalize the typed SSE event stream back into the existing handler
contract (content / thought / tool-call chunks) so the streaming
accumulator and OpenAI handler stay unchanged.
- Carry encrypted reasoning items across the in-turn tool loop
(provider-local, keyed by call_id) for stateless reasoning continuity.
- OPENAI_RESPONSES_STORE opt-in enables server-side state and
previous_response_id chaining across turns, persisted in message
metadata.
- Set gpt-5.5 to api_flavor: responses, reasoning_effort: medium.
- Remove AzureOpenAILLM and the azure_openai provider/catalog.
* fix: route remote-device tool through Redis so scheduled runs reach the device
The remote-device tool worked interactively but timed out on every scheduled
run. DeviceBroker was an in-process, in-memory singleton, but scheduled runs
execute in the Celery worker — a different process from the gunicorn web tier
that holds the device's SSE session — so a worker-side dispatch never reached
the device and the tool always hit its deadline.
Make the broker Redis-backed so every hop crosses the process boundary:
- queued commands -> Redis list dev:cmd:{device_id}
- output chunks -> Redis stream dev:out:{invocation_id}
- invocation metadata -> Redis hash dev:inv:{invocation_id}
- SSE upgrade tickets -> Redis key dev🎫{device_id}
Per-connection SSE session state stays in the web process. Reuses the existing
get_redis_instance()/CACHE_REDIS_URL; no new infrastructure. Also makes the web
tier safe to scale past one worker.
Concurrency hardening (from adversarial review + real-Redis e2e):
- XADD the output/control chunk before flipping completed=1, and have
drain_output do a final non-blocking flush after observing completion, so a
reader can't see completion and stop before the control chunk lands (this had
reintroduced the false "device did not respond (timed out)" under a race).
- _collect_result builds the result from drained chunks, checks the deadline
only after capturing a chunk, and falls back to the authoritative snapshot
(before cleanup) when no control chunk was observed.
- Audit outcome is written from locally-known fields so it survives the worker
racing to delete the invocation; a denied command now records a terminal
"denied" outcome instead of staying "dispatched".
- cmd-queue TTL raised to 900s (>= max drain deadline); dispatch-failure and
reaped-invocation cleanup; UTF-8 byte counts.
Tests: new tests/devices/{conftest (FakeRedis double), test_broker_cross_process,
test_broker_race, test_submit_output_audit}; drain/cleanup/ticket tests rewritten
for the Redis contract. The race tests fail against the pre-fix code. ruff clean;
device + tool-executor suites green.
* fix: log instead of silently passing on failed-dispatch cleanup
Addresses the code-quality lint on the best-effort hash delete in
dispatch_invocation's failure path: replace the bare `except: pass` with a
logger.debug carrying the invocation_id. No behavior change — cleanup stays
best-effort and still returns a failed Invocation.
* feat: make mobile popovers as sheet
* feat: sheets for other big modals mobile
* (fix):multiselectpopover, prevent placeholder re-render
* fix: google drive thing
* chore: push new notification text
* revert: remove vite dev server overrides
Drops the host/allowedHosts block added for local Tailscale testing —
it shouldn't have been committed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: ManishMadan2882 <manishmadan321@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): add Modal primitive on Radix Dialog (P1.1)
Adds frontend/src/components/ui/modal.tsx — a thin wrapper around
the existing Dialog/DialogContent primitives that preserves the
legacy WrapperModal visual contract (bg-card, rounded-2xl, p-8,
blurred backdrop) while inheriting Radix's focus-trap, body-scroll
lock, and Esc handling. Always renders a DialogTitle (wrapped in
VisuallyHidden when hideTitle is set) so modals stop logging a11y
warnings on adoption. No consumers migrated yet.
* refactor(frontend): migrate ConfirmationModal to Modal primitive (P1.2)
Swaps WrapperModal for the new Radix-backed Modal so consumers
inherit focus trap, body scroll lock, and Esc-to-close. Footer
DOM order flipped from [Submit, Cancel] (flex-row-reverse) to
[Cancel, Submit] so tab order now matches visual order; visual
layout unchanged. Message renders as DialogTitle for proper
screen-reader announcement.
* refactor(frontend): inline DeleteConvModal into Navigation (P1.3)
DeleteConvModal was a thin Redux wrapper around ConfirmationModal
with a single consumer and a dead useOutsideAlerter on an unbound
ref. Inlined the direct ConfirmationModal call at the call site
and deleted the wrapper file.
* refactor(frontend): migrate 5 small modals to Modal primitive (P1.4)
Migrates FolderManagementModal, AddActionModal, JWTModal,
ShareConversationModal and SearchConversationsModal off WrapperModal
onto the Radix-backed Modal so they gain focus trap, scroll lock,
and consistent DialogTitle a11y. Footer DOM order normalised so
tab order matches visual order. JWTModal stays intentionally
uncloseable via isPerformingTask; P1.7 revisits.
Also wraps DialogDescription in VisuallyHidden when no description
is provided, silencing the Radix "Missing Description" console
warning that consumers without a subtitle were triggering.
* refactor(frontend): migrate 5 medium modals to Modal primitive (P1.5a)
Migrates AddToolModal, AgentDetailsModal, ConfigToolModal,
ImportSpecModal and MoveToFolderModal off WrapperModal. They now
inherit focus trap, scroll lock, Esc-to-close and consistent
DialogTitle a11y. Footers reordered to natural DOM order so tab
order matches visual order. AddToolModal's dead useOutsideAlerter
on an unbound ref removed.
* refactor(frontend): migrate 5 large modals to Modal primitive (P1.5b)
Migrates PromptsModal, CustomModelModal, MCPServerModal,
ScheduleFormModal and Upload off WrapperModal. These were the last
remaining consumers of the legacy modal wrapper outside of the
shared WrapperModalPropsType (kept temporarily for P1.9 cleanup).
CustomModelModal continues to set isPerformingTask while saving;
P1.7 revisits Esc behavior during locked submits.
* refactor(frontend): migrate AgentTypeModal to Modal primitive (P1.6)
AgentTypeModal was the last hand-rolled fixed-inset overlay in the
codebase. Swapping to Modal gives it focus trap, Esc-to-close,
body scroll lock and consistent DialogTitle a11y — none of which
the legacy overlay implemented.
* fix(frontend): let users dismiss CustomModel and Schedule modals during save (P1.7)
Drops isPerformingTask on CustomModelModal and ScheduleFormModal so
Esc / click-outside / the X button all close the modal while a
save is in flight. The Save button itself stays disabled so users
can't double-submit. SchedulesView.closeModal was also gating on
submitting; that lock is removed too. The in-flight network call
continues in the background.
JWTModal remains intentionally uncloseable — it gates app access
on auth, not a transient save state.
* style(frontend): normalise modal CTA radius to rounded-3xl (P1.8)
ConfigToolModal, JWTModal, ShareConversationModal and
ScheduleFormModal previously used rounded-full for their primary
CTAs while the rest of the modal suite uses rounded-3xl. Switches
the four outliers and pads font-medium where missing. Footer DOM
order across all migrated modals was already corrected to natural
order during P1.2 / P1.4 / P1.5, so no further reorderings here.
* chore(frontend): delete legacy WrapperModal (P1.9)
All 14 consumers were migrated to the Radix-backed Modal primitive
across P1.2-P1.6. Drops the legacy WrapperModal.tsx, the orphan
WrapperModalPropsType type re-export, and updates the stale z-index
comment in TimezoneCombobox to reference Modal's z-50.
* feat(frontend): add floating-label variant to ui/input (P2.1)
Extends the shadcn Input primitive with an optional label prop and
leftIcon/required/labelBgClassName slots so consumers can swap the
legacy components/Input.tsx without losing the peer-based floating
label UX. Existing bare-input usage is unchanged.
* refactor(frontend): migrate 9 callers off components/Input.tsx (P2.2)
Switches ToolConfig, PromptsModal, AddActionModal, JWTModal,
ToolsPopup, SourcesPopup, MultiSelectPopup, FilePicker and Upload
to the ui/input floating-label variant. Drops legacy knobs that
the audit flagged as cruft (colorVariant, borderVariant, textSize,
edgeRoundness); inputs converge on rounded-md and 1px borders.
The biggest visible delta is PromptsModal's prompt-name field —
pill -> rounded-md rectangle, intentional.
* chore(frontend): delete legacy components/Input.tsx (P2.3)
All 9 consumers were migrated in P2.2. Removes the legacy
component and the unused InputProps type from components/types.
* refactor(frontend): migrate 7 raw text inputs to ui/input (P2.4)
Swaps the page-level search inputs on Sources, Tools, CustomModels
and AgentsList, plus the inline new-folder input on AgentsList,
the FolderManagementModal field, and SearchConversationsModal,
onto the ui/input primitive. Page searches now use the floating
label / leftIcon slot. Complex per-method or workflow-editor inputs
deferred. Also clears the trailing-newline lint error in
modals/types after the WrapperModalPropsType deletion.
* feat(frontend): add destructive-outline button variant (P2.5)
Adds the red-outlined danger button pattern (border-destructive
text-destructive on transparent ground, filling to bg-destructive
on hover) so callers like "Delete All" CTAs can adopt the
primitive instead of hand-rolling the class string.
* refactor(frontend): migrate settings/ buttons to ui/button (P2.6)
Replaces 10 raw <button> elements across General, Sources, Tools,
CustomModels, Prompts and ToolConfig with the ui/button primitive.
General's Delete-All CTA adopts the new destructive-outline variant.
Tiny method-row buttons inside ToolConfig with bespoke 50px inline
widths are deferred to a later targeted pass.
* refactor(frontend): migrate agents/ buttons to ui/button (P2.7)
31 raw <button> elements across AgentsList, NewAgent, the schedules
view + form + run drawer, SchedulerToolCallCard, RunLog,
SharedAgentCard, and WorkflowPreview converted to the ui/button
primitive. NewAgent's Danger-Zone Delete CTA adopts the new
destructive-outline variant. AgentTypeModal's two type cards stay
raw — the lucide icon sizes would conflict with Button's SVG
selector — but pick up focus-visible ring classes for a11y.
WorkflowBuilder (18 buttons) and PromptTextArea deferred.
* refactor(frontend): migrate modal + small-component buttons to ui/button (P2.8)
52 raw <button> elements across 11 modals (ConfirmationModal,
FolderManagementModal, AddActionModal, ConfigToolModal,
ImportSpecModal, ShareConversationModal, AgentDetailsModal,
MCPServerModal, CustomModelModal, JWTModal, SearchConversationsModal)
and 10 small components (ActionButtons, CopyButton,
TextToSpeechButton, Help, DocumentPagination, SettingsBar,
ConnectorAuth, UploadToast, Accordion, Notification) converted to
the ui/button primitive. ConfirmationModal's danger variant now
uses the canonical destructive variant rather than a class string.
MoveToFolderModal (purple-hex inputs) plus all chat/file-tree
internals deferred.
* refactor(frontend): collapse 3 popups into MultiSelectPopover (P2.9+P2.10)
Adds frontend/src/components/MultiSelectPopover.tsx — a single Radix
Popover + cmdk Command-backed primitive supporting groups, search,
icons, loading, footer slot, and (selected-first) ordering. Migrates
MessageInput (Sources + Tools), NewAgent (Sources + Tools + Models)
onto it, then deletes MultiSelectPopup, SourcesPopup and ToolsPopup
(~803 LoC removed). The isChatToolVisible helper moves to
utils/toolUtils.ts with its tests preserved. Visual change: drops
the bespoke Material 3-layer shadow in favor of shadow-md.
* refactor(frontend): collapse 3 custom dropdowns onto shadcn primitives (P2.11)
Deletes Dropdown.tsx, DropdownMenu.tsx and DropdownModel.tsx
(~513 LoC) and migrates the 10 consumers (Hero, Sources, Prompts,
PromptsModal, Analytics, ToolConfig, General, ShareConversationModal,
Upload, NewAgent). Simple cases land on ui/select; action menus on
ui/dropdown-menu; the searchable prompts picker uses ui/popover +
ui/command. Inline edit/delete on prompts rows preserved via a small
combobox; variable menus in PromptsModal use ui/dropdown-menu.
* refactor(frontend): migrate Sidebar + ArtifactSidebar to ui/sheet (P2.12)
Deletes Sidebar.tsx and rewrites ConversationBubble to render the
citations panel directly via ui/sheet. ArtifactSidebar keeps its
public API but its overlay variant now uses ui/sheet instead of a
hand-rolled fixed-position overlay + manual click-outside, so it
inherits Radix's focus trap, Esc handling, and scroll lock.
* refactor(frontend): migrate ContextMenu to ui/dropdown-menu (P2.13)
Deletes ContextMenu.tsx (177 LoC) and migrates its 8 consumers
(ConversationTile, FolderCard, AgentCard, settings/CustomModels,
settings/Tools, settings/Sources, FileTree, ConnectorTree) to the
Radix-backed ui/dropdown-menu. Destructive items use the canonical
variant; each menu trigger uses stopPropagation so clicking the
3-dots no longer fires the parent row's onClick.
* refactor(frontend): retire the legacy color palette (P0.1+P0.2)
Migrates the last 13 consumers of legacy named-color tokens to
the semantic system (bg-primary, bg-muted, text-foreground,
border-border, etc.) and then deletes the ~60-token legacy
@theme block in index.css, including the invalid 5-digit
--color-just-black: #00000 entry. Three intentional visual
shifts: ToggleSwitch on-state moves from green to bg-primary,
PageNotFound CTA from the misnamed bg-blue-1000 to bg-primary,
and the user-message bubble gradient from medium-purple/slate-blue
to violet-500/violet-600.
* refactor(frontend): replace hardcoded purple hexes with semantic tokens (P0.3)
Removes #7D54D1 / #976af3 / #6A4DF4 / #563DD1 / #8C67D7 / #6F3FD1
/ #D9534F literals from ~15 tsx files, mapping each to bg-primary /
text-primary / border-primary / bg-destructive / hover:bg-primary/90.
The Spinner primitive drops its color prop and inherits currentColor
so it picks up the parent's Tailwind text class. Analytics chart
colors now read --primary at runtime via getComputedStyle on
document.body (where the .dark class lives) so dark mode renders
#976af3 instead of the light #7d54d1.
PromptsModal's bespoke #D9534F danger maps to bg-destructive.
MoveToFolderModal's purple-branded inputs and several toast banners
were also caught in the sweep.
* feat(frontend): add --font-mono token, drop inline IBMPlexMono styles (P0.4)
Defines a --font-mono token in @theme and points it at the existing
IBMPlexMono-Medium @font-face fallback chain. The 6 inline
style={{ fontFamily: 'IBMPlexMono-Medium' }} occurrences in
ConversationBubble's tool-call panels are replaced with the
font-mono Tailwind utility; .logs-table now references the token
too. Tokenises the mono surface so future consumers don't need
inline styles.
* feat(frontend): token-ize modal + toast shadows (P0.5)
Adds --shadow-modal and --shadow-toast tokens in @theme. The two
arbitrary shadow strings (the WrapperModal-era 4-40 modal shadow
and the 24-48 toast shadow) collapse to `shadow-modal` and
`shadow-toast` utilities across Modal, WorkflowBuilder's settings
panel, UploadToast and ToolApprovalToast. The one-off radial hover
halo in Upload.tsx stays inline (not part of the modal/toast set).
* refactor(frontend): collapse 49 arbitrary radii onto canonical scale (P0.6)
Maps every rounded-[Npx] (and its rounded-t-*/rounded-b-* variants)
to a Tailwind built-in: rounded-md/lg/xl/2xl/3xl/full. Includes
NewAgent's 11 panel sections (rounded-2xl), AgentCard/FolderCard
(rounded-2xl), Hero demo CTAs and message-input pills (rounded-full),
the answer/thought/user-message bubbles (rounded-2xl/3xl), code
blocks (rounded-md/xl), and the FileTree/ConnectorTree search
chrome. Closes out the audit's "six different radius scales"
finding.
* refactor(frontend): standardize type scale + fix font typos (P0.7)
Collapses ~80 arbitrary text-[Npx] utilities onto the canonical
Tailwind scale (text-xs/sm/lg/xl/2xl/3xl/4xl), fixes the
font-semi-bold typo in SharedConversation (rendered as font-normal
before), and removes the undefined font-inter class from 10 sites —
body already defaults to Inter via index.css. The largest visual
shift is text-[10px] -> text-xs (2px growth on tiny chip labels);
mid-range deltas are <=1px.
* style(frontend): soften popover/select/dropdown/command radii to rounded-xl
The pill-shaped (rounded-3xl, effectively fully rounded at h-9/h-10)
select triggers in Settings/Analytics/ToolConfig/NewAgent looked
mismatched against the shadcn-default rounded-md (6px) popover
content. Bumps the four ui content panels to rounded-xl (12px) so
the trigger -> popover transition reads as a single step on the
radius scale rather than an 18px jump.
* style(frontend): align Select popover to trigger width, merge Hero model picker
ui/select: switches default position from item-aligned to popper so
Radix exposes --radix-select-trigger-width on the popper container,
adds w-(--radix-select-trigger-width) so the popover width tracks
the trigger exactly, and drops the min-w-32 floor that was forcing
narrow triggers like the Analytics filter (w-[125px]) to render
3px wider than their triggers. Side-effect: Analytics dropdowns no
longer overlap the settings tab bar (popper anchors below).
Hero: pairs the trigger's data-[state=open]:rounded-b-none with
rounded-t-none + rounded-b-4xl + data-[side=bottom]:translate-y-0
on the popover so the two shapes merge into one continuous pill
when open.
* refactor(frontend): fold two skeletons into SkeletonLoader (P3.1)
Adds connectedState + filesSection variants to SkeletonLoader and
sweeps the existing variants from bg-gray-200/700/bg-gray-300/600
onto the semantic bg-muted / bg-muted-foreground/20 pair. Deletes
the two standalone files and migrates GoogleDrivePicker to the
variant API. One skeleton tone across the app.
* chore(frontend): drop legacy spinner SVGs + last raw animate-spin (P3.2)
Migrates Navigation, ShareConversationModal, TextToSpeechButton
and MultiSelectPopover off three SVG spinner assets onto the
currentColor Spinner component, then deletes assets/spinner.svg,
spinner-dark.svg and Loading.svg. Also replaces the last raw
animate-spin border-b-2 loader with Spinner.
* refactor(frontend): inline SVG components -> assets/*.svg?react (P3.3)
Deletes SendArrowIcon.tsx and RetryIcon.tsx. The send-arrow path
moves into assets/send.svg (overwriting the unused paper-plane
icon), the retry path into a new assets/retry.svg; both use
fill="currentColor" so consumers theme via Tailwind text classes.
MessageInput and ConversationMessages now import the SVGs via
?react. ConversationMessages drops a now-unused useDarkTheme hook
that was switching the retry icon's fill/stroke manually.
documentation-dark / no-files-dark / science-spark-dark variants
left in place — they're hand-tuned dark palettes, not pure inverts,
so collapsing them would regress visually.
* refactor(frontend): unify close icons on lucide X (P3.4)
Migrates the last three assets/exit.svg consumers (MessageInput
attachment pills, ArtifactSidebar split + sheet variants,
ConversationTile rename cancel) plus two inline-SVG X paths
(UploadToast dismiss, ToolApprovalToast dismiss) to lucide X.
Deletes assets/exit.svg. lucide X uses currentColor so the
filter dark:invert plumbing also goes away. ConversationTile's
bare clickable img becomes a proper button.
* refactor(frontend): merge Avatar + AgentImage into ui/avatar (P3.5)
Builds a single components/ui/avatar.tsx that subsumes both legacy
files: takes src/alt/fallbackSrc/className/imgClassName for the
smart-img case, and falls back to children passthrough for the
custom-node case (e.g. the user-question avatar in
ConversationBubble). All 6 consumers (Navigation, AgentCard,
SharedAgentCard, SharedAgent, ConversationBubble x5, ResearchProgress)
migrated, then Avatar.tsx and AgentImage.tsx deleted. The onError
robot-fallback behavior is preserved.
* refactor(frontend): promote ToggleSwitch/SettingsBar/Accordion/Table -> ui/* (P3.6)
Promotes four legacy components to shadcn-style primitives:
- ui/switch (Radix Switch) replaces ToggleSwitch; 7 callsites
migrate, including 3 inline hand-rolled toggles in NewAgent.
- ui/tabs (Radix Tabs) replaces SettingsBar; the settings page
keeps its mobile horizontal-scroll arrow wrapper.
- ui/accordion (Radix Accordion) replaces Accordion; consumed by
ConversationBubble tool-call panels.
- ui/table (plain <table>) replaces Table; tokenises bg-gray-100
-> bg-muted and borders -> border-border; 3 tree-browser
consumers swap imports.
Adds @radix-ui/react-switch / -tabs / -accordion as explicit deps
(previously transitive via the radix-ui meta-package).
* refactor(frontend): dedupe FileTree + ConnectorTree (P3.7)
Extracts a shared TreeBrowser into components/tree/ that handles
directory state, breadcrumb header, search dropdown, table render,
Chunks pickup, row-menu API and an SSE-terminal-wait hook. The
two wrappers shrink to thin shells holding only what differs: file
upload + delete flow on FileTree, sync flow on ConnectorTree. The
Sources.tsx consumer API is unchanged. 1895 -> 1391 LoC (~27%
reduction).
* style(frontend): unify settings card padding to p-5 (P4.1)
Sources tiles were p-3, Tools tiles p-6, CustomModels p-5. Picks
p-5 as the median and applies to Sources and Tools so the three
tile families breathe the same way.
* style(frontend): promote Settings Prompts Add to filled CTA (P4.2)
The Add prompt button was visually outlined-as-primary (variant=outline
+ border-primary/text-primary/hover:bg-primary overrides). Adding a
prompt is the page's main action — drop the overrides and use the
canonical default (filled bg-primary) variant so the CTA reads as
primary at a glance.
* style(frontend): add dark variants to WorkflowBuilder palette tiles (P4.3)
The five node-picker tiles (AI Agent purple, End green, Note yellow,
Set State blue, If/Else orange) used bg-{color}-100 with no dark:
companion, leaving them blindingly bright on the dark canvas. Pairs
each with bg-{color}-900/40 + dark:text-{color}-300 so they sit on
the dark workflow background.
* refactor(frontend): dedupe METHOD_COLORS + extract NARROW_CELL (P4.4)
ToolConfig and ImportSpecModal both declared an identical
METHOD_COLORS map for HTTP-verb pills. Moves the map + a
getMethodColorClass helper into utils/httpMethodColors.ts and
points both consumers at it.
ToolConfig also had 9 identical style={{ width: '50px', ... }}
inline blocks on table cells. Replaces them with a NARROW_CELL
Tailwind constant. The `!` suffix is required because the
codebase's @utility table-default rule otherwise out-specifies
the utility classes — inline styles were load-bearing for that
reason.
* style(frontend): drop MoveToFolderModal inline fonts + tokenize hover (P4.5)
Removes the inline fontFamily 'Inter' / 'Segoe UI' overrides — body
already inherits Inter via index.css — and collapses the inline
font-size/line-height/letter-spacing on the title into Tailwind
utilities (text-2xl leading-7 tracking-[0.15px]). Replaces the
breadcrumb hardcoded text-[#59636E] with text-muted-foreground and
swaps the chevron's hover:bg-[#FFFFFF2B] for hover:bg-accent.
* refactor(frontend): migrate ToolConfig method-row buttons to ui/button (DEF.1)
13 raw <button> elements left over from the P2.6 settings sweep
(method-row trash inside NARROW_CELL, rename confirm/cancel,
Add/Cancel/Add-new footers in the Properties + Headers tables)
now go through the ui/button primitive. Action-row trash retains
its stopPropagation so clicking the icon doesn't collapse the row.
* refactor(frontend): migrate conversation/* buttons to ui/button (DEF.2)
21 raw <button> elements across ConversationBubble (15),
ConversationMessages (2), ConversationTile (2), SharedConversation
(1) and ResearchProgress (1) now go through ui/button. Includes
question collapse/edit, edit submit/cancel, like/dislike feedback,
copy / regen / retry, citation pills, tool-call approve/deny,
sources/thought/tool-calls accordions, scroll-to-bottom and the
shared-conversation CTA. All stopPropagation handlers preserved.
* refactor(frontend): migrate MessageInput buttons to ui/button (DEF.3)
Migrates the 7 raw <button> elements in the 1768-LoC chat input
(attachment chip remove, Sources/Tools popover triggers + footer
upload CTA, voice/mic toggle, stop-streaming, send/submit) onto
ui/button. The 3 remaining <input> elements are file/dropzone
inputs (react-dropzone hidden + attach + voice picker) and are
intentionally left as-is.
* refactor(frontend): migrate WorkflowBuilder buttons + inputs (DEF.4)
17 of 18 raw <button> elements migrated to ui/button (back-to-all,
pencil-edit, settings Done, toolbar Details/Access-Details/Delete/
Preview/save, error-alert close, node-config close, state-op add/
delete, condition case add/delete, simple/advanced segmented control,
delete-node). The 1 remaining raw button is the custom toggle
switch for allow_system_prompt_override which uses a non-button
animated UI.
7 of 8 raw <input> migrated to ui/input (workflow name, node title,
output variable, target variable, case name, simple-mode var/value).
The 1 remaining raw input is the stream_to_user checkbox.
Palette tiles, drag-and-drop, React Flow node interactions all
preserved.
* refactor(frontend): migrate utility-component buttons to ui/button (DEF.5)
25 raw <button> elements across 7 small utility components migrated
to ui/button: Chunks (8), MermaidRenderer (5, low-risk path keeps
the custom download wrapper), GoogleDrivePicker (3),
FilePicker (3), tree/TreeBrowser (2), workflow PromptTextArea (2),
ArtifactSidebar (2). All onClick/disabled/aria/stopPropagation
preserved; MermaidRenderer's brand colors and Drive-picker's
#A076F6 brand override kept inline.
* refactor(frontend): migrate page-level buttons to ui/button (DEF.6)
27 raw <button> elements across Navigation (6), preferences
PromptsModal (5), Upload (3), settings Prompts (3), MoveToFolderModal
(8) and ToolApprovalToast (2) migrated to ui/button. Includes the
sidebar collapse / new-chat / mobile-hamburger / agent-pin row in
Navigation, both Add and Edit prompt modal flows, the breadcrumb +
folder-row + Cancel/Move CTAs in MoveToFolderModal, Upload back
chevron / advanced toggle / Train submit, the toast Dismiss + Review
pair, and the Settings Prompts popover trigger + per-row pencil/trash
buttons.
* refactor(frontend): final raw <input> sweep onto ui/input (DEF.7)
25 text/search/number inputs migrated to ui/input across:
ToolConfig (14 method-row + search), ImportSpecModal (1 spec URL),
NewAgent (agent name + 2 advanced number limits), Chunks (2
searches), MoveToFolderModal (new-folder pill), ConversationTile
(inline rename), ConversationBubble (tool-call denial reason),
tree/TreeBrowser (file search with rounded-t-3xl switch), and the
workflow PromptTextArea variable search.
11 raw <input> intentionally left: checkboxes (4), dropzone/file
inputs (3 - controlled by react-dropzone), the ScheduleFormModal
borderless title input (1), and the ui/input + ui/time-picker
primitives themselves (2).
Per-site className overrides keep method-row inputs tight (h-auto
rounded-lg px-2 py-1 shadow-none), borderless inline renames, and
the MoveToFolderModal primary-purple pill.
* chore(frontend): drop orphan dark-variant SVG assets (DEF.8)
Removes 5 unused color-variant SVGs that no consumer imports:
clock-purple, clock-white, monitoring-purple, monitoring-white,
white-trash. The remaining hand-tuned dark variants (documentation
pair, no-files pair, science-spark pair) are intentionally kept
because their consumers use <img> and collapsing them onto a single
currentColor SVG would require a JSX refactor outside this scope.
* fix(a11y): make AgentCard + FolderCard keyboard accessible (P5.1)
Both wrapped their entire body in a bare <div onClick> with no
keyboard handler — invisible to keyboard and screen-reader users.
Adds role="button", tabIndex={0}, Enter/Space onKeyDown, and a
focus-visible ring (focus-visible:ring-ring/50 ring-[3px]) so
the cards are actually focusable. AgentCard only takes the role
when its status is 'published' so unpublished cards stay
non-interactive. FolderCard exposes aria-pressed for its
expanded state.
* fix(a11y): add focus-visible rings to remaining outline-none sites (P5.2)
8 textareas/inputs without replacement focus visuals (PromptsModal
content, ToolConfig rename pair, NewAgent description + system
prompt, workflow PromptTextArea, ConversationBubble edit-query,
Chunks editor) now pair their outline-none with the canonical
focus-visible:ring-ring/50 focus-visible:border-ring focus-visible:
ring-[3px] pattern. 7 callsites in ScheduleFormModal +
WorkflowBuilder upgraded from focus: to focus-visible: so the
ring only shows on keyboard focus, not on mouse click.
* fix(a11y): gate changelog banner off /share/ and /shared/ (P5.3)
The Notification banner used to render on every route as long as
showNotification was true. Embedded / public share pages
(/share/<id>, /shared/agent/<id>, /shared/...) shouldn't carry
product chrome. Adds a useLocation() check in App.tsx so the
banner only renders on application routes.
* feat(frontend): sidebar collapse -> icon rail on desktop (P6.1)
Replaces the previous "sidebar slides off-screen, only a tiny
floating header remains" collapse behaviour with a vertical
56px-wide icon rail at the left edge on lg+ viewports. The rail
surfaces the main nav actions (open-sidebar, new-chat when
applicable, search, agents, settings). Main content shifts to
lg:ml-14 when collapsed and lg:ml-72 when expanded so it never
underlaps the rail. Mobile / tablet behaviour is unchanged —
they still use the hamburger drawer.
* fix(frontend): lift changelog banner above mobile input bar (P6.2)
The Notification banner used uniform bottom-6 (24px), which on
mobile sat squarely over the Sources / Tools / Attach button row
(37px overlap into the textarea). Switches to bottom-24 sm:bottom-6
so the banner sits ~96px above the bottom on mobile (clearing the
input bar) and stays at 24px on tablet+. Now only a ~11px sliver
grazes the textarea's top border on mobile, with no controls
occluded.
* refactor(frontend): extract MessageInput sub-components (P6.3)
Splits the 1919-LoC MessageInput.tsx into 5 focused sub-components
under components/message-input/: AttachmentChipList (the file-chip
strip above the textarea), AttachFileButton (paperclip + hidden
file input), SourcesTrigger (pill + MultiSelectPopover for source
docs), ToolsTrigger (pill + lazy-loaded MultiSelectPopover for
tools), and MicButton (idle/recording/transcribing tri-state).
Parent shrinks to 1686 LoC; ~233 lines of UI logic move to 6 new
files. State stays in the parent — sub-components are props-only.
The textarea+send/stop button and the voice-capture plumbing
intentionally remain inline (would require prop-drilling /
hook-lifting beyond the conservative bar for this pass).
* docs(agents): document the icon strategy for new code (P6.4)
Adds an "Icons" subsection under the frontend coding rules in
AGENTS.md. Codifies the three current sources (lucide-react, SVG
?react components, SVG <img>) and picks lucide-react as the default
for new UI affordances, SVG ?react for brand/domain illustrations
that need currentColor theming, and discourages <img src=...> for
new icons. Notes the three remaining hand-tuned dark-variant SVG
pairs that intentionally stay paired (documentation, no-files,
science-spark) because they're multi-color illustrations, not pure
inverts.
* fix(frontend): align Settings Prompts Add button height to dropdown trigger
The Add button was rendering 10px shorter (36px) than the
Active-Prompt PopoverTrigger (46px) because the Button primitive's
default h-9 was capping it. Adds h-auto + py-3 so padding drives
the height, plus border border-transparent so the 1px border the
trigger has is counted on both — taking Add from 36 -> 46px and
closing the visible bottom-edge mismatch.
* feat: auto version release
* fix(frontend): clean up workflow-builder header duplication
The toolbar was rendering the workflow name twice (once in the
AgentPageHeader breadcrumb and once in a standalone title block)
plus the description awkwardly stacked next to the tab strip. Drops
the title block on the owner view (breadcrumb already shows the
name); the pencil edit button stays but is now always visible
instead of hidden until hover, and gains a "name — description"
tooltip so the description is still reachable at a glance. Non-
owners keep the title block since they don't see the breadcrumb.
* fix: tabs
* fix: breadcrumb and tabs
* fix: remove details btn on agent workflow
* fix: dropdown arrow
* fix: modal clicks
* fix: rounded search
* fix: mini issues
* fix: pin versions
* (chore) fe lint-fix
* fix: rounded search on source, password autocomplete, accordions in chat
---------
Co-authored-by: ManishMadan2882 <manishmadan321@gmail.com>
* Harden protection with pinned requests and path-param encoding
* fix: domain pinning
* fix: tests
* fix: html test 2
---------
Co-authored-by: Alex <a@tushynski.me>