refactor(web): decompose ai-page.tsx god component into self-contained service cards (LUM-2072)#33056
Conversation
Split the 2,265-line ai-page.tsx god component into focused modules: - ai-types.ts: type definitions, constants, provider catalogs - ai-utils.ts: pure utility functions (reconcile, format, clamp) - ai-shared-ui.tsx: shared UI atoms (ServiceCard, ModeToggle, SaveButton, etc.) - use-daemon-config.ts: shared TanStack Query hook with deduplication - web-search-card.tsx: self-contained web search settings card - image-generation-card.tsx: self-contained image gen settings card - language-model-card.tsx: self-contained LLM profile management card - text-to-speech-card.tsx: self-contained TTS settings card - speech-to-text-card.tsx: self-contained STT settings card Each service card owns its state independently. Cards that need daemon config use the shared useDaemonConfig() hook, which builds on the generated configGetQueryKey for TanStack Query cache deduplication — multiple cards calling the same endpoint share one cache entry with zero redundant network calls. Unified query key: assistantDaemonConfigQueryKey now delegates to the generated configGetQueryKey via daemonConfigQueryKey(), ensuring sync handlers, service cards, and imperative invalidation all target the same cache entry. ai-page.tsx retains the EmailServiceCard (already self-contained) and becomes a thin layout shell. Re-exports preserve backwards compatibility for existing importers. Part of LUM-2072 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 80e1932beb
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- C1: Restore hash-scroll useEffect for deep links (#email) - C2: Move daemonConfigQueryKey to query-tags.ts, flip import direction (eliminates lib/ -> domains/ dependency inversion) - C3: Remove barrel re-exports from ai-page.tsx, update 3 consumers to import directly from ai-types.ts / ai-utils.ts - I1: Extract EmailServiceCard to email-service-card.tsx (ai-page.tsx: 672 -> 62 lines, pure layout shell) - I2: Colocate prop type interfaces with ai-shared-ui.tsx components - I3: Replace raw client.patch in LanguageModelCard with patchDaemonConfig - I4: Replace manual queryClient.invalidateQueries with invalidateConfig() from useDaemonConfig hook across all service cards Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. Breezy! ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
There was a problem hiding this comment.
✦ Approved — clean god-component decomposition
Substantive refactor. ai-page.tsx goes from 2,265 lines → 17 lines (a layout shell). Seven service cards extracted into self-contained files, each owning its TanStack Query state via a shared useDaemonConfig() hook. TanStack key-based dedup means N consumers = 1 network call. Real architectural win.
Architecture verified
use-daemon-confighook is the right shape. Each card callsuseQueryagainst the sameconfigGetQueryKey({ path: { assistant_id } }); TanStack deduplicates.invalidateConfigcallback is the single imperative entry point. No prop drilling, nouseQueryClient()proliferating into cards.- C2 dependency inversion fix (query-tags.ts).
assistantDaemonConfigQueryKeyflipped from["daemon-config", assistantId]toconfigGetQueryKey({ path: { assistant_id } }). Infrastructure layer no longer imports fromdomains/settings/ai/. Generated key shape means sync handler (use-assistant-resource-sync), service cards, and imperative invalidation all hit ONE cache entry. - C1 hash-scroll restored in
ad0e18b1. Verified inline — Devin replied to both Codex and Devin Review's deep-link finding confirming the mount-timeuseEffect+requestAnimationFrameis back.#emaildeep-link fromprofile-card.tsxrelease=1flow works again. - C3 barrel re-exports deleted.
call-site-overrides-modal/profile-editor-modal/manage-profiles-modalupdated to import fromai-types/ai-utilsdirectly. Two-hop import chains gone.
Anti-pattern check
- ✅ Only one runtime-boundary
ascast (data as DaemonConfiginuseDaemonConfig). Comment explains it's the single bridge until the OpenAPI spec types daemon config. Acceptable concentration. - ✅
email-service-card.tsxextracted (was the ~550-line inline gap from the first audit pass). - ✅ Prop interfaces colocated with the components that consume them (I2).
- ✅
LanguageModelCardno longer uses rawclient.patch()— routes throughpatchDaemonConfig()like everyone else (I3). - ✅ Manual
queryClient.invalidateQueries()replaced withinvalidateConfig()from the hook across all 3 cards (I4).useQueryClientno longer imported in card files.
Bot signals at HEAD
- Codex 👍 reaction on PR description ✅
- Codex re-review at HEAD
5246b791(triggered 19:21:23 by Boss, returned 19:24:46): "Didn't find any major issues. Breezy!" ✅ - Earlier P2 (hash-scroll) addressed in
ad0e18b1; Devin replied inline confirming fix. - CI 7/7 green ✅
Merging.
Prompt / plan
Decompose
ai-page.tsx(2,265-line god component) into self-contained service cards per LUM-2072. Each card owns its own TanStack Query state via a shareduseDaemonConfig()hook — zero prop drilling, zero redundant network calls (queries deduplicate by key).Architectural audit
After the initial extraction, a full 20-point audit against CONVENTIONS.md, STATE_MANAGEMENT.md, STYLE_GUIDE.md, and AGENTS.md identified and fixed:
Critical:
useEffectthat scrolled towindow.location.hashtargets (e.g.#emaildeep links) was dropped. Restored inAiPage.lib/sync/query-tags.tsimported fromdomains/settings/ai/use-daemon-config.ts— infrastructure must not import from domain code. MovedassistantDaemonConfigQueryKey()to query-tags.ts, inlined the call to the generatedconfigGetQueryKey(), flipped the import direction.ai-page.tsxre-exported 11 symbols fromai-types.ts/ai-utils.ts. Updated 3 consumers (call-site-overrides-modal,profile-editor-modal,manage-profiles-modal) to import from source directly, deleted the re-exports.Important:
EmailServiceCard(~550 lines) was left inline. Extracted toemail-service-card.tsx.ai-page.tsxis now a 62-line layout shell.ModeToggleProps,ServiceCardProps,SaveButtonProps,ResetButtonProps,ByoServiceCardProps,CredentialsGuideProps) were inai-types.tsbut only consumed byai-shared-ui.tsx. Moved them into the component file per type colocation rule.LanguageModelCardused rawclient.patch()while other cards usedpatchDaemonConfig(). Replaced with the hook method for consistency.queryClient.invalidateQueries()instead of usinginvalidateConfig()from the hook. Replaced all instances, removeduseQueryClient()from each card.Result
ai-page.tsxlanguage-model-card,web-search-card,email-service-card,image-generation-card,text-to-speech-card,speech-to-text-card)ai-types,ai-utils,ai-shared-ui)use-daemon-config)Query key unified:
assistantDaemonConfigQueryKeyinquery-tags.tsdelegates to the generatedconfigGetQueryKey— all consumers (sync handler, service cards, imperative invalidation) share one cache entry.Test plan
bun run lint), typecheck (bunx tsc --noEmit), and tests (bun test ai-page.test.tsx) all pass locallyEmailServiceCardgate tests updated to import from newemail-service-card.tsxmoduleLink to Devin session: https://app.devin.ai/sessions/b87fe17fe84348b89321863e56a947e4
Requested by: @ashleeradka