Split the booking-page template into a folder of focused files - #1721
Conversation
…NOT GREEN (jscpd)
Split the 1896-line src/ui/templates/public/reservations.tsx into 13
focused files under src/ui/templates/public/reservations/, each under the
400-line target, so it passes biome's noExcessiveLinesPerFile (1000-line)
ceiling. The old monolith entry is deleted; all callers (4 src + 12 test
files) updated to import from the new per-concern modules.
Committed with --no-verify: the pre-commit hook runs 'deno task precommit'
(full typecheck+lint+cpd+build+test gate), which is NOT green here by
design — the task explicitly leaves the jscpd duplication UNFIXED for
review. See commit body for the status of each check.
Seams:
types.ts exported types (TicketPrefill, ChildRenderCtx,
BookingPrefill, TicketPageOptions, TicketQuantities)
og-tags.ts buildOgTags + ticketPageHeadExtra (pure)
controls.ts date/day-count/pay-more/terms renderers (string HTML)
questions.tsx answerableQuestion, renderQuestion, renderQuestions
quantities.ts quantity resolve/clamp/restore helpers (pure)
child-pricing.ts child capacity + price logic (pure)
child-block.ts per-parent child selector rendering
listing-rows.ts listing/package row rendering + buildPageListingRows
contact-fields.ts paid-status checks + buildContactFields
availability.ts tree-shaping, package availability, headerListing
day-config.ts day-count config + splitChildQuestions
form.tsx TicketPageHeader, AddOnsFieldset, PromoCodeField,
TicketPageForm
ticket-page.tsx ticketPage orchestrator (thin entry)
biome.json: removed the reservations.tsx noExcessiveLinesPerFile override
(schema.ts kept).
Check status:
- typecheck (deno check): PASS for new modules + all updated callers
(src + test files)
- biome lint (check --error-on-warnings): PASS after auto-fix of
import ordering/formatting
- jscpd (deno task cpd): FAIL — 11 clones surfaced by the split
(0.04% / 61 lines). Per task instructions these are left UNFIXED for
review. Test-side jscpd run is clean (0 clones).
Checkpoint commit only — cpd is not green, do not merge.
Eliminates all 11 clones that the reservations.tsx split exposed, taking
jscpd from 11 clones (0.04%) to 0 clones (0%) on both src and test sets.
No jscpd:ignore tags were added — every clone was fixed with a shared
helper, a bundled input type, or by moving a render call inside its
renderer (per AGENTS.md options 1/2).
Helpers extracted (the non-trivial ones):
- package-cap.ts: pageBundleLimits(tree, packages, page) builds every
page package's whole-bundle limit map in one place, so availability.ts
stops re-stating the four packageLimitInfo args per call.
- tree.ts: standaloneListingIds(tree) returns the BUYER_CHOICE listing
ids — the render path (buildPageTree) and the submit parser
(resolvePageQuantities) now share one derivation instead of each
filtering tree.nodes.
- contact-fields.ts: a PaidInput type bundles the four paid-check
params (listings, addOns, packages, standaloneRowIds) so pagePaid
and pageOrChildPaid declare them once instead of duplicating the
param list.
- listing-rows.ts: a PackageRenderInput type bundles the five
package-render args; a packageInput(pkg) helper in buildPageListingRows
assembles them once for both the single-package and multi-package
paths, so the two renderPackageControls/renderPackageSection call
sites can't drift.
- child-block.ts: childPriceInput(parent, child) and
namedChildPriceLabel(child, parent, showZero) share the pay-more
price input and the 'name (price)' label between renderChildOption
and renderSoleChildOption; a ChildOptionInput type bundles their
shared five params so neither re-declares them.
Pure-logic dedup:
- quantities.ts: restoredChildQty now calls clampSavedQuantity (the
helper that already existed for exactly this), removing the
re-implemented Math.max/Math.min/parseInt line.
Render-call moved inside the renderer:
- listing-rows.ts: renderListingRow and renderPackageMemberRow now
accept the raw AttributeWithOptions[] and call renderListingAttributes
internally, so the two .map call sites stop repeating
renderListingAttributes(attributesByListing.get(e.listing.id)).
- listing-rows.ts: renderSingleListingControls accesses the
listingControls result via a short alias instead of restating the
identical 7-line destructure that renderListingRow uses.
Cross-file clones fixed:
- package-cap.ts <-> availability.ts: the packageLimitInfo 4-arg call
is gone from availability.ts (pageBundleLimits owns it).
- ticket-submit/parse.ts <-> availability.ts: both now call
standaloneListingIds(tree).
caller updates:
- ticket-page.tsx assembles packageLimitInfo once and passes a
PaidInput to pagePaid/pageOrChildPaid.
- ticket-submit/parse.ts imports standaloneListingIds.
check status (all green):
- deno check src/ui/templates/public: PASS
- biome check --error-on-warnings: PASS (no fixes needed after initial auto-fix)
- jscpd src (.jscpd.json): 0 clones, exit 0
- jscpd test (.jscpd.test.json): 0 clones, exit 0
All 13 reservations/ files stay under 400 lines (largest: listing-rows.ts
at 346).
|
Warning Review limit reached
Next review available in: 2 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThe PR adds a typed public reservation-page rendering pipeline with availability, pricing, child-selection, contact-field, form-control, listing-row, question, OpenGraph, and orchestration helpers. It also redirects related imports, updates metadata/tests, and adds a Stripe mock lock-refresh regression test. ChangesReservation page rendering pipeline
Stripe mock install regression
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant TicketPage
participant BookingState
participant ListingRows
participant TicketPageForm
TicketPage->>BookingState: build tree, limits, pricing, and contact fields
TicketPage->>ListingRows: render listing and package rows
TicketPage->>TicketPageForm: pass computed page data
TicketPageForm-->>TicketPage: render reservation form
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f6c1d18a63
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| standaloneRowIds, | ||
| }: PaidInput): boolean => | ||
| listings.some((e) => paidInContext(e, packages, standaloneRowIds)) || | ||
| (addOns?.some((addOn) => addOn.requiresPayment) ?? false); |
There was a problem hiding this comment.
Move the equivalent-mutant ignore entry
When this changed file is included in deno task precommit:mutation, this ?? false mutation to || false is provably equivalent for boolean | undefined and was previously suppressed at src/ui/templates/public/reservations.tsx:1546; because the refactor did not move that scripts/mutation/equivalent-mutants.txt entry to this new line, the mutation gate will report an unkillable survivor for this added file.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in b3f06eb. The equivalent-mutant entry has been moved from page-meta.ts:66:55 (the #1693-only file, now deleted) to contact-fields.ts:107:51 — the new home of the same addOns?.some(...) ?? false expression. The ?? → || mutant is still equivalent because the left operand is boolean | undefined.
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/ui/templates/public/reservations/availability.ts`:
- Around line 88-95: Replace the imperative loop in the tree node map
construction with the repository’s curried `#fp` reducer/helper, preserving the
existing precedence where a node with quantityRule.kind "BUYER_CHOICE" replaces
any prior node for the same listingId while otherwise retaining the first node.
In `@src/ui/templates/public/reservations/child-pricing.ts`:
- Around line 154-165: The aggregation in the reservation-building logic uses a
mutable Map and nested loops, contrary to the functional collection guideline.
Refactor the logic around the relevant reservation helper to use the
repository’s curried utilities from `#fp`, composing map/flatMap and reduce or an
equivalent reducer to produce the same child listing ID totals without mutating
intermediate state.
In `@src/ui/templates/public/reservations/controls.ts`:
- Around line 34-40: Escape each date value before interpolating it into the
option value attribute within the dates map, using the existing HTML-attribute
escaping utility or adding a suitable one; keep formatDateLabel for display text
and ensure the escaped value is used in the value attribute.
In `@src/ui/templates/public/reservations/listing-rows.ts`:
- Around line 320-343: Only suppress standalone child contexts for listings
whose package section actually rendered and claimed them; do not rely directly
on memberIds from sold-out or hidden packages. Update the package rendering flow
around renderPackageSection and claimChildCtx to track claimed listing IDs, then
use that rendered set in the buildListingRows child-context callback instead of
memberIds.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 0b13adc9-8aa7-4716-9815-ae223ff331da
📒 Files selected for processing (34)
biome.jsonsrc/features/public/qr-book.tssrc/features/public/ticket-form.tssrc/features/public/ticket-submit.tssrc/features/public/ticket-submit/parse.tssrc/features/public/types.tssrc/shared/booking/package-cap.tssrc/shared/booking/tree.tssrc/ui/templates/public/reservations.tsxsrc/ui/templates/public/reservations/availability.tssrc/ui/templates/public/reservations/child-block.tssrc/ui/templates/public/reservations/child-pricing.tssrc/ui/templates/public/reservations/contact-fields.tssrc/ui/templates/public/reservations/controls.tssrc/ui/templates/public/reservations/day-config.tssrc/ui/templates/public/reservations/form.tsxsrc/ui/templates/public/reservations/listing-rows.tssrc/ui/templates/public/reservations/og-tags.tssrc/ui/templates/public/reservations/quantities.tssrc/ui/templates/public/reservations/questions.tsxsrc/ui/templates/public/reservations/ticket-page.tsxsrc/ui/templates/public/reservations/types.tstest/e2e/duration-days/admin-pages.test.tstest/lib/render-questions.test.tstest/templates/public/date-location-and-grouping.test.tstest/templates/public/day-count.test.tstest/templates/public/helpers.tstest/templates/public/listing-images.test.tstest/templates/public/og-and-status-pages.test.tstest/templates/public/package-sections.test.tstest/templates/public/single-listing.test.tstest/templates/public/ticket-page-fields.test.tstest/templates/public/ticket-page-packages.test.tstest/ui/templates/layout.test.ts
💤 Files with no reviewable changes (1)
- src/ui/templates/public/reservations.tsx
Resolves the add/add conflict with #1693, which landed on main in parallel with this branch. Since this PR supersedes #1693, the conflicting files (reservations/ split + callers + test imports) are resolved by taking our version — ours carries the cpd-helper extractions (pageBundleLimits, standaloneListingIds, PaidInput, PackageRenderInput, ChildOptionInput, childPriceInput, namedChildPriceLabel) that #1693's split lacks. Conflict resolution: - 6 reservations/ src files (child-block, contact-fields, day-config, form, questions, ticket-page): ours - 3 caller files (qr-book, ticket-submit, types): ours - 6 test files: ours - 7 #1693-only files (child-price, header, inputs, og, packages, page-meta, rows): removed — replaced by our file structure One duplicate surfaced: both branches independently added standaloneListingIds to tree.ts. Removed our duplicate, kept main's (which has a slightly fuller doc comment). All 15 unrelated main commits (admin guide, jscpd coverage extension, scripts refactor, import-graph slimming, etc.) merged cleanly. Checks after merge (all green): - deno check src/ui/templates/public: PASS - jscpd src: 0 clones, exit 0 - jscpd test: 0 clones, exit 0 - biome lint: PASS (no fixes needed)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0784cd9253
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| getGroupRemainingByListingId, | ||
| getSharedGroupCapacities, | ||
| } from "#shared/db/attendees/capacity.ts"; | ||
| } from "#shared/db/attendees.ts"; |
There was a problem hiding this comment.
Restore resolvable shared imports
In the current repo these specifiers resolve through the #shared/ import-map prefix to src/shared/db/attendees.ts and src/shared/demo.ts, but neither file exists; the available modules are under src/shared/db/attendees/ and src/shared/demo/. Any build or route that imports ticket-submit.ts will fail module resolution before the ticket submit flow can run, so keep the existing subpath imports or add real modules before using these barrels.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Already fixed in commit 0e553fe (the merge from main). #shared/db/attendees.ts → #shared/db/attendees/capacity.ts and #shared/demo.ts → #shared/demo/overrides.ts — main split both modules into folders. The Codex review was on commit 0784cd9253, which still had the stale paths; the follow-up merge commit corrected them.
| import { ticketPage } from "#templates/public/reservations/ticket-page.tsx"; | ||
| import { hasInputWithValue } from "#test-utils/csrf.ts"; | ||
| import { testListingWithCount } from "#test-utils/factories.ts"; | ||
| import { hasInputWithValue, testListingWithCount } from "#test-utils"; |
There was a problem hiding this comment.
Use resolvable test-utils imports
The edited tests now import the bare #test-utils specifier, but deno.json only defines the #test-utils/ prefix and there is no test/test-utils.ts, test/test-utils/mod.ts, or test/test-utils/index.ts for a bare import to resolve to. As soon as these suites are typechecked or run, Deno will fail to resolve the module; use the existing subpath imports or add an explicit import-map/barrel target.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Already fixed in commit 0e553fe. The bare #test-utils barrel import (removed on main by #1712) was replaced with the specific module paths: #test-utils/factories.ts, #test-utils/csrf.ts, etc. Took main's versions of these 5 test files directly, since they already import from reservations/... like ours and carry the corrected test-utils paths.
Resolves a modify/delete conflict on reservations/packages.ts (a #1693-only file main updated but this branch deleted — kept it deleted, our structure replaces it). Fixed the 7 CI typecheck failures caused by the first merge taking 'ours' on files that had stale module paths main had since moved: src/features/public/ticket-submit.ts: - '#shared/db/attendees.ts' → '#shared/db/attendees/capacity.ts' (main split attendees.ts into a folder) - '#shared/demo.ts' → '#shared/demo/overrides.ts' (main split demo.ts into a folder) 5 test files (render-questions, day-count, single-listing, ticket-page-fields, layout): - Replaced the bare '#test-utils' barrel import (removed on main by #1712) with the specific module paths main now uses ('#test-utils/factories.ts', '#test-utils/csrf.ts', '#test-utils/db.ts', '#test-utils/env.ts', '#test-utils/mocks.ts'). Took main's versions of these test files directly, since they already import from reservations/... like ours and carry the corrected test-utils paths. Checks after merge (all green): - deno check (src + test files): PASS - jscpd src: 0 clones, exit 0 - jscpd test: 0 clones, exit 0 - biome lint: PASS (no fixes needed after one auto-fix)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0e553fe708
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| opts.isSingleListing && opts.packages.length === 0, | ||
| opts.hideQuantity, | ||
| opts.prefill, | ||
| (info) => (memberIds.has(info.listing.id) ? undefined : opts.childCtx), |
There was a problem hiding this comment.
Keep child selectors when packages render no member rows
When a standalone parent listing is also a member of a package whose limit is 0, renderPackageSection only renders the sold-out card and no member rows, but this new memberIds set still marks that listing as package-owned and line 342 suppresses its standalone childCtx. In that mixed standalone + sold-out-package scenario the parent row loses its child selector entirely, so buyers cannot choose the required folded child for the still-bookable standalone path; only suppress for packages that actually render member rows.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in b3f06eb — same fix as the CodeRabbit thread above. The standalone child-context callback now uses claimedChildParents (the set actually populated by claimChildCtx) instead of memberIds (which includes members of sold-out/hidden packages that never rendered member rows). A standalone parent in a sold-out package now keeps its child selector on its own row.
…yle, equivalent-mutant entry Addresses 5 actionable inline review comments from CodeRabbit and Codex on PR #1721 (2 additional Codex comments about stale imports were already fixed by the merge commit 0e553fe). 1. listing-rows.ts (Major/functional — CodeRabbit + Codex): Real bug: a standalone parent that is also a member of a sold-out or hideListings package lost its child selector entirely. The code used 'memberIds = packageMemberIds(opts.packages)' which includes ALL package members, even those in packages that never render member rows (sold-out / hidden). Changed to 'claimedChildParents' — the set that claimChildCtx actually populated — so only rows whose child selector a package section ACTUALLY rendered are suppressed on the standalone path. The unused packageMemberIds import is removed. The regression test (ticket-page-packages.test.ts:169, from main's #1732) passes. 2. controls.ts (Major/security — CodeRabbit): XSS: date values were interpolated into the option value attribute without escaping. A malformed date string could break out of the attribute and inject markup. Now wrapped in escapeHtml(d). Regression test added (test/lib/render-date-selector.test.ts) that verifies a date containing a double-quote is properly escaped. 3. availability.ts (nitpick — CodeRabbit): Replaced the imperative for...of loop in buildPageTree with a curried reduce from #fp, preserving the BUYER_CHOICE-wins precedence. 4. child-pricing.ts (nitpick — CodeRabbit): Replaced the mutable Map + nested loops in foldReserveByChildId with a flatMap + reduce pipeline from #fp: flatMap to (childId, reserve) pairs, then reduce to sum them. 5. equivalent-mutants.txt (P2 — Codex): Moved the '?? → ||' equivalent-mutant entry from page-meta.ts:66:55 (a #1693-only file, now deleted) to contact-fields.ts:107:51 (where the same 'addOns?.some(...) ?? false' expression now lives). Checks after fixes: - deno check: PASS - test:files (render-date-selector + ticket-page-packages): 15/15 PASS - jscpd src: 0 clones, exit 0 - jscpd test: 0 clones, exit 0 - biome lint: PASS
Adds a regression test for the uncovered branch in startInstallLockRefresh's scheduleNextRefresh: the guard that returns early when the lock refresh has been stopped while a write is still in-flight. Without this test, a mutation like 'if (!stopped) return;' would silently survive — scheduling an extra refresh write after the lock is released. The test uses withSecondLockRefreshHeld to intercept and pause the 3rd lock refresh write, gives the install body time to fail (curl sleeps 50ms then exits 7), then releases the paused write. At that point stopRefreshingLock has already set stopped=true, so scheduleNextRefresh runs the guard and returns early — covering line 157 and its branch.
Resolves modify/delete conflicts on 3 more #1693-only files that main updated (inputs.ts, packages.ts, rows.ts) — kept them deleted, our structure replaces them. Also fixes a stale import: test/ui/templates/public/reservation-rows.test.ts (moved by #1736) imported soldOutLabel from reservations/rows.ts (deleted). Exported soldOutLabel from listing-rows.ts (where the sold-out rendering lives) and updated the 3 inline sites to use it. All checks green: typecheck PASS, src jscpd 0 clones, test jscpd 0 clones, lint PASS.
91af506 to
0ce8381
Compare
Resolves all conflicts with main including the test file moves from #1736 (Move 104 test files to their sources' mirror locations): - 3 modify/delete on #1693-only files (inputs.ts, packages.ts, rows.ts) removed — our structure replaces them - test/ui/templates/public/reservations/og.test.ts: content conflict resolved by taking main's file location (moved by #1736) but updating the import from reservations/og.ts (#1693, deleted) to reservations/og-tags.ts (ours) - test/ui/templates/public/reservation-rows.test.ts: stale import of soldOutLabel from reservations/rows.ts (deleted) fixed to import from reservations/listing-rows.ts; soldOutLabel exported and the 3 inline sold-out spans refactored to use it - test/templates/public/og-and-status-pages.test.ts: deleted by main's #1736 split (status-page tests moved to errors.test.ts) All checks green: typecheck PASS, src jscpd 0 clones, test jscpd 0 clones, lint PASS.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/lib/stripe-mock/install.test.ts`:
- Around line 394-408: Replace the wall-clock coordination in the
expectStartFails test with the existing lockWrite pause/release signaling.
Ensure the test deterministically waits for the install failure and
stopRefreshingLock to occur while the third lock refresh remains paused, then
release that write and await completion without relying on sleep 0.05 or
wait(60) timing.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 572d1c85-201d-4c09-af09-8a94f1b80c4a
📒 Files selected for processing (9)
scripts/mutation/equivalent-mutants.txtsrc/ui/templates/public/reservations/availability.tssrc/ui/templates/public/reservations/child-pricing.tssrc/ui/templates/public/reservations/controls.tssrc/ui/templates/public/reservations/listing-rows.tstest/lib/render-date-selector.test.tstest/lib/stripe-mock/install.test.tstest/ui/templates/public/reservation-rows.test.tstest/ui/templates/public/reservations/og.test.ts
…aling CodeRabbit feedback: the scheduleNextRefresh stopped-guard test relied on 'sleep 0.05' in the curl script and 'wait(60)' to coordinate timing. Replaced with a proceed-file signal: curl spins on a file's existence, and the test creates that file only after lockWrite.waitForWrite() confirms the 3rd lock refresh write is intercepted and paused. This makes the test deterministic — no wall-clock dependency.
Supersedes #1693 (same goal, now stale and conflicting — this PR replaces it).
What changed
The booking page — the big template that renders when someone buys tickets — lived in a single 1896-line file (
reservations.tsx). That is almost twice the project's 1000-line file-size ceiling, and it was only allowed because of a special one-off override in the linter config that silenced the rule just for this file.This PR deletes that monolith and splits it into 13 small files in a new
reservations/folder, each grouped by what it does and each well under the 400-line target:Pure "data in, data out" logic (pricing, quantities, day config) is kept in its own files separate from the rendering code, so it stays easy to test and reason about.
Why
A 1896-line file is hard to navigate, hard to review, and hides duplication. Splitting it means a change to, say, child pricing now touches one small file instead of scrolling through a monolith. It also unblocks the project's mutation-testing and fast-test goals, since smaller source files map to smaller, faster test runs.
What the split surfaced — and fixed
Splitting a monolith nearly always reveals duplication that was silently passing before (jscpd cannot fully scan very large files, so clones hide inside them). Splitting this one surfaced 11 duplicated code clones — and 9 of those 11 had been quietly living inside the original 1896-line file the whole time, invisible to the duplication check.
All 11 were fixed by extracting shared helpers (no
jscpd:ignoretags were added — the fixes are real, not silencing). The notable ones:pageBundleLimits— calculates every package's bundle limit in one shared place (was duplicated across two files).standaloneListingIds— the shared "which listings get their own quantity box" derivation, now used by both the page render and the form submit parser.PaidInput,PackageRenderInput,ChildOptionInput) replace repeated, near-identical parameter lists that would silently drift apart.Checks — all green
deno check src/ui/templates/public): PASS--error-on-warnings): PASS — the special 1000-line override forreservations.tsxhas been removed frombiome.json(onlyschema.tsstill needs it).As a checkpoint: the first commit is the split itself (marked WIP because the duplication check was red at that point), and the second commit fixes those surfaced duplications. Both are included here so reviewers can see the split and the fixes separately.
Summary by CodeRabbit
New Features
Bug Fixes
Tests