Skip to content

refactor(dorm): isolate the dorm feature in its own workspace package - #230

Merged
Arcadi4 merged 11 commits into
mainfrom
refactor/dorm-package
Oct 3, 2026
Merged

Arcadi4 merged 11 commits into
mainfrom
refactor/dorm-package

Conversation

@a2751012962

@a2751012962 a2751012962 commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

中文摘要

把宿舍功能拆成独立的 workspace 包 @iguide/dorm(packages/dorm),web 侧只通过一个懒加载路由 /dorms/* 接触它:

  • 以前四个宿舍 Provider 包在整个应用外面:打开 /chat 也会加载宿舍代码、去 Supabase 拉全部宿舍;Layout 也直接依赖住房筛选状态。
  • 现在 web 只通过 apps/web/src/pages/dorms/DormRoute.tsx 挂载它;包本身不从 web 导入任何东西。
  • 实测:/chat 加载的宿舍模块从 14 个降到 0;所有页面都要加载的主入口 chunk 从 106.95 kB 降到 87.91 kB。
  • 离开 /dorms 再回来,筛选、搜索、对比列表和已加载的宿舍数据都会保留(不会重新查询),刷新页面才重置——和改动前一致。

Why

The dorm feature was half of apps/web/src (84 of 167 files) and was wired into the app shell: App.tsx wrapped every page in four dorm providers, Layout.tsx read housing filter state and rendered the dorm list's mobile header, and SidebarPanel.tsx rendered DormSidebar directly. So a dorm change, bug, or slow query reached pages that have nothing to do with dorms.

What changed

packages/dorm (@iguide/dorm) holds the feature: UI, state, services, dorm SQL migrations, data scripts (vp dlx tsx, documented in packages/dorm/AGENTS.md), and the review scrapers. Files keep their old relative layout, so 88 of the 108 moves are pure renames with history intact. Mapbox GL, react-map-gl, rc-slider, and Headless UI are now the package's dependencies and are gone from the web app.

The boundary runs one way: the host passes what the package needs, the package imports nothing from the host.

  • configureDormServices({ supabase, streamChatResponse }) lends the host's Supabase client and the chat stream. The package never creates its own client, because a second GoTrue client would race the host's for the same session.
  • <DormRoutes language user requestLogin layout /> is mounted at /dorms/*, with all dorm providers inside it.
  • layout gives the package the flying-heart target refs plus two slot setters. The dorm sidebar and the list's mobile header portal into those slots, which lets them render in the shell while still reading dorm context — a plain slot node can't do that.

Web app: App.tsx has no dorm providers left, Layout.tsx lost 133 lines of housing code, SidebarPanel renders a generic sidebarSlot (a new LayoutContext field mirroring mobileHeaderSlot), and apps/web/src/pages/dorms/DormRoute.tsx is the only web file that knows the package's API. @iguide/ui holds Typewriter, which both the chat sidebars and dorm use.

Also: index.css adds @source for both packages, and the built CSS lost exactly the 0.25 kB of dorm-only CSS that moved into the dorm chunk, so the class set is unchanged. dorm_scripts/ moved to packages/dorm/scrapers/; its output paths resolved to a nonexistent <root>/web/src and its Python env path pointed at the legacy app/, so both now point at the package and at apps/web/.env.local.

Behavior changes

  • None for dorm state. Moving the providers under /dorms/* would have reset filters, search, the compare list, and the loaded dorms on every return; the last commit keeps them for the page session (useRetainedState), and a reload still clears them. The filter modal and compare overlay open flags are deliberately not retained, so returning never pops them back open.
  • Unknown paths under /dorms/* (e.g. /dorms/a/b) now redirect to /dorms; before, they fell through to /chat.

Verification

All checks below compare against a clean origin/main worktree.

Check origin/main This branch
vp lint (type-aware) 0 errors, 221 warnings 0 errors, same 221 warnings
tsc --noEmit web ✓ web ✓, @iguide/dorm ✓, @iguide/ui ✓
vp build (apps/web) ✓ ✓
Main entry chunk index.js 106.95 kB 87.91 kB
Housing modules loaded on /chat (dev) 14 0
vp dlx tsx scripts/validate-dorm-data.ts passed, 23 dorms passed, 23 dorms

Every commit builds, typechecks, and lints clean on its own.

In the browser (dev server): /chat makes no dorm requests and loads no dorm modules; /dorms lazy-loads the package and renders the dorm sidebar in the shell; at mobile width the search, filter, sort, and view controls render in the shell header and typing keeps focus; /dorms/:id renders; navigating back to /chat removes the sidebar and leaves no placeholder; and with search isr plus list view, a round trip through /chat keeps both and getAllDorms ran once in total.

Not exercised: my local env has no Supabase or Mapbox keys, so both origin/main and this branch show an empty list and "map unavailable". Real dorm data, the map, reviews, and admin editing still need a quick look on a preview deploy.

Review guide

refactor(ui) extracts Typewriter. refactor(dorm) is the move and the boundary — focus on packages/dorm/src/{index.ts,DormRoutes.tsx,services/host.ts,contexts/DormHostContext.tsx}, apps/web/src/pages/dorms/DormRoute.tsx, and the Layout/SidebarPanel/LayoutContext diffs; the rest is moves. Then chore(dorm) for the scrapers, docs, ci, fix(dorm) for the retained state (see Behavior changes), and three React Doctor fixes (hook deps, LayoutContext value, sort-button aria-label) with no runtime change.

Notes

The dorm feature is moving into its own workspace package and still needs Typewriter, so the component moves out of the web app into a package both can depend on.
Dorm providers wrapped the whole app, so every page loaded dorm code and fetched every dorm from Supabase on startup, and the shell layout reached into housing state. The feature now lives in its own package behind one lazy /dorms/* route: the host lends it the Supabase client, session, chat stream, and two shell slots, and the package imports nothing from the app. Its dependencies (Mapbox GL, rc-slider, Headless UI) leave the web app with it. Filters, search, and the compare list now reset when you leave /dorms; favorites and history persist through localStorage as before.
They write the dorm package's googleReviews.ts, so they now live beside it. Their output paths and the Python env path pointed at directories that no longer exist (../web/src and the legacy app/); they now resolve to the package and apps/web/.env.local.
Dorm SQL, seed, and data-script docs now sit with the package that owns them, and the repo guides describe the boundary: the web app reaches dorm code only through its /dorms adapter.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅

View logs ↗
No Preview URL. Enable ↗ d187c7b 2026-10-02T07:10:32.933Z View logs ↗
  • Build: Failed ❌

View logs ↗
82f76c3 2026-10-02T06:55:15.136Z View logs ↗
  • Build: Failed ❌

View logs ↗
5482948 2026-10-02T06:29:23.991Z View logs ↗
  • Build: Failed ❌

View logs ↗
c80aef3 2026-10-02T06:26:28.382Z View logs ↗
  • Build: Failed ❌

View logs ↗
d68bf88 2026-10-02T06:19:05.404Z View logs ↗
  • Build: Failed ❌

View logs ↗
48575eb 2026-10-02T06:04:58.373Z View logs ↗

@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

React Doctor found 116 new issues in 32 files · 9 errors & 107 warnings · score 44 / 100 (Critical) · 127 fixed · vs main

Errors

107 warnings

scripts/audit-dorm-media.ts

  • ⚠️ L70 await inside a loop async-await-in-loop

src/Typewriter.tsx

  • ⚠️ L1 Full Framer Motion import use-lazy-motion
  • ⚠️ L51 Array index used as a key no-array-index-as-key

src/components/housing/AIChat.tsx

  • ⚠️ L2 Full Framer Motion import use-lazy-motion
  • ⚠️ L19 Large component is hard to read and change no-giant-component
  • ⚠️ L136 Control missing accessible label control-has-associated-label
  • ⚠️ L308 Control missing accessible label control-has-associated-label

src/components/housing/DormCard.tsx

  • ⚠️ L310 React function has high control-flow complexity no-high-complexity-react-function
  • ⚠️ L343 Click handler missing keyboard handler click-events-have-key-events
  • ⚠️ L343 Interaction on static element no-static-element-interactions

src/components/housing/DormComparison.tsx

  • ⚠️ L1 Full Framer Motion import use-lazy-motion
  • ⚠️ L83 Large component is hard to read and change no-giant-component
  • ⚠️ L276 Array index used as a key no-array-index-as-key
  • ⚠️ L295 Array index used as a key no-array-index-as-key
  • ⚠️ L324 Control missing accessible label control-has-associated-label
  • ⚠️ L351 Control missing accessible label control-has-associated-label
  • ⚠️ L433 Control missing accessible label control-has-associated-label
  • ⚠️ L456 Array index used as a key no-array-index-as-key
  • ⚠️ L605 Control missing accessible label control-has-associated-label
  • ⚠️ L628 Array index used as a key no-array-index-as-key
  • ⚠️ L694 Click handler missing keyboard handler click-events-have-key-events

src/components/housing/DormDetail.tsx

  • ⚠️ L1 Full Framer Motion import use-lazy-motion
  • ⚠️ L57 Large component is hard to read and change no-giant-component
  • ⚠️ L57 React function has high control-flow complexity no-high-complexity-react-function
  • ⚠️ L81 State initializer runs on every render rerender-lazy-state-init
  • ⚠️ L100 Effects chained together no-effect-chain
  • ⚠️ L102 Missing effect dependencies exhaustive-deps
  • ⚠️ L104 Effects chained together no-effect-chain
  • ⚠️ L146 Control missing accessible label control-has-associated-label

src/components/housing/DormDetailFloorPlans.tsx

  • ⚠️ L1 Full Framer Motion import use-lazy-motion
  • ⚠️ L45 Large component is hard to read and change no-giant-component
  • ⚠️ L107 Array lookup inside a loop js-set-map-lookups
  • ⚠️ L164 Click handler missing keyboard handler click-events-have-key-events
  • ⚠️ L173 Click handler missing keyboard handler click-events-have-key-events
  • ⚠️ L173 Interaction on static element no-static-element-interactions
  • ⚠️ L369 AnimatePresence unmounts with its exiting child motion-animate-presence-must-outlive-child
  • ⚠️ L413 Click handler missing keyboard handler click-events-have-key-events
  • ⚠️ L413 Handler on non-interactive element no-noninteractive-element-interactions

src/components/housing/DormDetailGallery.tsx

  • ⚠️ L1 Full Framer Motion import use-lazy-motion
  • ⚠️ L66 Click handler missing keyboard handler click-events-have-key-events
  • ⚠️ L66 Interaction on static element no-static-element-interactions

src/components/housing/DormDetailHeader.tsx

  • ⚠️ L1 Full Framer Motion import use-lazy-motion
  • ⚠️ L21 React function has high control-flow complexity no-high-complexity-react-function

src/components/housing/DormDetailInfo.tsx

  • ⚠️ L1 Full Framer Motion import use-lazy-motion

src/components/housing/DormDetailReviews.tsx

  • ⚠️ L1 Full Framer Motion import use-lazy-motion
  • ⚠️ L38 Large component is hard to read and change no-giant-component
  • ⚠️ L38 React function has high control-flow complexity no-high-complexity-react-function
  • ⚠️ L256 AnimatePresence unmounts with its exiting child motion-animate-presence-must-outlive-child

src/components/housing/DormList.tsx

  • ⚠️ L1 Full Framer Motion import use-lazy-motion

src/components/housing/DormMap.tsx

  • ⚠️ L120 Large component is hard to read and change no-giant-component

57 more warnings not shown.

Reviewed by React Doctor for commit d187c7b. See inline comments for fixes.

Moving the providers under the /dorms route made them unmount on every visit elsewhere, resetting filters, search, the compare list, and the loaded dorms. Their state now outlives the route for the page session, as it did when the providers wrapped the whole app; a reload still clears it.
React Doctor's exhaustive-deps cannot tell that useRetainedState returns React's stable setter, so it flagged every callback and memo using one. Listing them changes nothing at runtime and clears those warnings.
The provider built a new value object on every Layout render, re-rendering every useLayout consumer, including the dorm route, even when nothing they read had changed.
Main pruned package scripts in favor of vp built-ins and rewrote the docs. The dorm docs now use vp dlx tsx from packages/dorm, and the new packages drop their scripts to match.
Main moved operational docs from the READMEs into AGENTS.md. The dorm database and data-script sections it added to apps/web/AGENTS.md now live in a new packages/dorm/AGENTS.md with package paths, and the root architecture and deploy notes point there.
@@ -1,14 +1,14 @@
import { Typewriter } from "@iguide/ui"
import { motion, AnimatePresence } from "framer-motion"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

React Doctor · react-doctor/use-lazy-motion (warning)

Importing "motion" ships about 30 kb of extra code and slows page load. Use "m" with LazyMotion instead.

Fix → Use import { LazyMotion, m } from "framer-motion" with domAnimation features. Saves about 30kb.

Docs

@@ -1,3 +1,4 @@
import { Typewriter } from "@iguide/ui"
import { AnimatePresence, motion } from "framer-motion"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

React Doctor · react-doctor/use-lazy-motion (warning)

Importing "motion" ships about 30 kb of extra code and slows page load. Use "m" with LazyMotion instead.

Fix → Use import { LazyMotion, m } from "framer-motion" with domAnimation features. Saves about 30kb.

Docs

@Arcadi4
Arcadi4 added this pull request to stack #233 October 3, 2026 06:36
@Arcadi4
Arcadi4 merged commit 2cbefc4 into main Oct 3, 2026
3 checks passed
@Arcadi4
Arcadi4 deleted the refactor/dorm-package branch October 3, 2026 06:39
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.

2 participants