Skip to content

refactor(web): decompose ai-page.tsx god component into self-contained service cards (LUM-2072)#33056

Merged
vex-assistant-bot[bot] merged 4 commits into
mainfrom
devin/1780424961-lum-2072-ai-page-decomposition
Jun 2, 2026
Merged

refactor(web): decompose ai-page.tsx god component into self-contained service cards (LUM-2072)#33056
vex-assistant-bot[bot] merged 4 commits into
mainfrom
devin/1780424961-lum-2072-ai-page-decomposition

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

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 shared useDaemonConfig() 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:

  • C1 — Hash-scroll regression: Mount-time useEffect that scrolled to window.location.hash targets (e.g. #email deep links) was dropped. Restored in AiPage.
  • C2 — Dependency inversion: lib/sync/query-tags.ts imported from domains/settings/ai/use-daemon-config.ts — infrastructure must not import from domain code. Moved assistantDaemonConfigQueryKey() to query-tags.ts, inlined the call to the generated configGetQueryKey(), flipped the import direction.
  • C3 — Barrel re-exports: ai-page.tsx re-exported 11 symbols from ai-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:

  • I1 — Incomplete extraction: EmailServiceCard (~550 lines) was left inline. Extracted to email-service-card.tsx. ai-page.tsx is now a 62-line layout shell.
  • I2 — Type colocation: 6 prop interfaces (ModeToggleProps, ServiceCardProps, SaveButtonProps, ResetButtonProps, ByoServiceCardProps, CredentialsGuideProps) were in ai-types.ts but only consumed by ai-shared-ui.tsx. Moved them into the component file per type colocation rule.
  • I3 — Inconsistent mutation: LanguageModelCard used raw client.patch() while other cards used patchDaemonConfig(). Replaced with the hook method for consistency.
  • I4 — Manual invalidation duplication: 3 cards manually called queryClient.invalidateQueries() instead of using invalidateConfig() from the hook. Replaced all instances, removed useQueryClient() from each card.

Result

File Before After
ai-page.tsx 2,265 lines (god component) 62 lines (layout shell)
Service cards 0 files 7 files (language-model-card, web-search-card, email-service-card, image-generation-card, text-to-speech-card, speech-to-text-card)
Shared modules 0 files 3 files (ai-types, ai-utils, ai-shared-ui)
Query hook 0 files 1 file (use-daemon-config)

Query key unified: assistantDaemonConfigQueryKey in query-tags.ts delegates to the generated configGetQueryKey — all consumers (sync handler, service cards, imperative invalidation) share one cache entry.

Test plan

  • Lint (bun run lint), typecheck (bunx tsc --noEmit), and tests (bun test ai-page.test.tsx) all pass locally
  • CI checks: lint, typecheck, build, tests
  • Existing EmailServiceCard gate tests updated to import from new email-service-card.tsx module

Link to Devin session: https://app.devin.ai/sessions/b87fe17fe84348b89321863e56a947e4
Requested by: @ashleeradka

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>
@linear

linear Bot commented Jun 2, 2026

Copy link
Copy Markdown

LUM-2072

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread apps/web/src/domains/settings/ai/ai-page.tsx

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment thread apps/web/src/domains/settings/ai/ai-page.tsx
ashleeradka and others added 3 commits June 2, 2026 19:02
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>
@ashleeradka

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

ℹ️ 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".

@vex-assistant-bot vex-assistant-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✦ 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-config hook is the right shape. Each card calls useQuery against the same configGetQueryKey({ path: { assistant_id } }); TanStack deduplicates. invalidateConfig callback is the single imperative entry point. No prop drilling, no useQueryClient() proliferating into cards.
  • C2 dependency inversion fix (query-tags.ts). assistantDaemonConfigQueryKey flipped from ["daemon-config", assistantId] to configGetQueryKey({ path: { assistant_id } }). Infrastructure layer no longer imports from domains/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-time useEffect + requestAnimationFrame is back. #email deep-link from profile-card.tsx release=1 flow works again.
  • C3 barrel re-exports deleted. call-site-overrides-modal/profile-editor-modal/manage-profiles-modal updated to import from ai-types/ai-utils directly. Two-hop import chains gone.

Anti-pattern check

  • ✅ Only one runtime-boundary as cast (data as DaemonConfig in useDaemonConfig). Comment explains it's the single bridge until the OpenAPI spec types daemon config. Acceptable concentration.
  • email-service-card.tsx extracted (was the ~550-line inline gap from the first audit pass).
  • ✅ Prop interfaces colocated with the components that consume them (I2).
  • LanguageModelCard no longer uses raw client.patch() — routes through patchDaemonConfig() like everyone else (I3).
  • ✅ Manual queryClient.invalidateQueries() replaced with invalidateConfig() from the hook across all 3 cards (I4). useQueryClient no 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.

@vex-assistant-bot vex-assistant-bot Bot merged commit 0b30894 into main Jun 2, 2026
7 checks passed
@vex-assistant-bot vex-assistant-bot Bot deleted the devin/1780424961-lum-2072-ai-page-decomposition branch June 2, 2026 19:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant