feat: wire components to render from merged contract data (#37) #45
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "37-wire-components-to-data"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Replace the three hardcoded
{#if}branches in +page.svelte with a new data-driven rendering path that reads from the merged config object. Whendata.configis non-null (team hascontract_configpopulated), components render dynamically from the config. When null, the existing hardcoded branches serve as fallback.Changes
src/routes/contract/[token]/+page.svelte— Added{#if data.config}data-driven rendering branch above the existing fallback. Updated Queens branding$effectto also checkdata.config?.variant === 'girls-travel'.src/lib/contract-config.ts— ExtendedTournamentinterface withdates,cost,local,travel,entry_fee,travel_notefields. AddedTournamentTravelinterface. AddedoptionaltoPractice. RenamedPaymentScheduletoPaymentScheduleItemwithlabel/datefields. Addedvariantandstatic_contenttoContractConfigandMergedConfig. Updated merge function to propagate new fields.src/lib/types.ts— Updated re-export fromPaymentScheduletoPaymentScheduleItem.Test Plan
npm test— 30/30 tests passnpm run check— zero new errors (12 pre-existing type errors in db.ts/minio.ts/server.ts unrelated to this change)contract_configstill render the hardcoded contract variantscontract_configpopulated render from the merged configReview Checklist
Related Notes
None.
Related
Closes #37
PR #45 Review
DOMAIN REVIEW
Tech stack: SvelteKit / TypeScript (Svelte 5 with
$effectrunes)Interface changes in
contract-config.ts:PaymentScheduleItemmakes ALL fields optional (due_date?,amount?,description?,label?,date?). This means aPaymentScheduleItemof{}is valid at the type level. The template usesp.label ?? p.description ?? ''andp.date ?? p.due_date ?? ''which handles the fallback, but renders empty strings when both are missing -- producing blank rows in the payment schedule. Consider making at least one oflabel/descriptionrequired, or adding a runtime filter.Tournamentinterface has overlapping fields:dateANDdates,feeANDcostANDentry_fee. The template usest.dates ?? t.date ?? ''which works, but the interface allows setting both. A discriminated union or a single canonical field per concept would be cleaner. Non-blocking since config data is developer-controlled.TournamentTravelhas an index signature[key: string]: number | undefinedwhich defeats type safety -- any misspelled key silently becomes valid.Template complexity in
+page.svelte:TournamentTriprendering at line ~230 has a dense inline expression: This is business logic (key formatting, filtering) embedded in markup. Should be extracted to a helper function for readability and testability.Math.round(data.config.monthly_fee * 25 / 30 / 5) * 5uses magic numbers (25, 30, 5) inline in the template. This rounding-to-nearest-$5 logic should be a named function with documentation explaining the formula.XSS concern -- raw HTML injection:
PracticeSchedulerows inject raw HTML via string concatenation:p.location + ', ' + p.time + (p.optional ? ' <span class="schedule-optional">(optional)</span>' : ''). If thedetailprop inPracticeSchedulerenders via{@html}, this is an XSS vector when config data comes from the database. If it renders via{detail}(text interpolation), the HTML will display as literal text, which is a different bug (visible tags). Either way this needs attention -- the component should handle the optional badge internally rather than injecting HTML strings from the caller.Static content paragraph splitting:
data.config.static_content[key].split('\\n\\n')-- this splits on the literal four-character string\n\n, not on actual newline characters. If config data contains real newlines (\n), this split will never fire and the entire block renders as one paragraph. If the data literally contains escaped backslash-n sequences, it works. This depends on how the config is stored/serialized. Verify which representation the database uses.Accessibility:
BLOCKERS
mergeContractConfigfunction now propagatesvariantandstatic_contentvia conditional spread. The existing 30 tests invalidation.test.tscover signature validation only -- zero tests exercise the merge function. This is new functionality (config merging drives what parents see on a legal contract) with zero test coverage. Per BLOCKER criteria: new functionality with zero test coverage.NITS
Extract inline business logic from template. The
Object.entries().filter().map()chain for tournament travel rows, the title-case transformationkey.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '), and the prorated fee calculation should all be helper functions. This improves readability and makes the logic testable in isolation.PaymentScheduleItemall-optional fields. Consider requiring at leastlabelORdescriptionto prevent rendering blank payment rows. A union type or runtime guard would be appropriate.TournamentTravelindex signature. The[key: string]: number | undefinedindex signature weakens type safety. If the intent is to allow arbitrary cost categories, that is acceptable but should be documented. If the categories are known, remove the index signature.Conditional spread in merge function. The pattern
...(teamConfig.variant ? { variant: teamConfig.variant } : {})is repeated three times (variant, static_content, contract_version). A cleaner approach: build an object of optional fields and spread once, or use a small helper that omits undefined keys.Raw HTML in PracticeSchedule rows. Rather than injecting
<span>tags via string concatenation, passoptional: trueas a data field (already on thePracticeinterface) and let thePracticeSchedulecomponent handle rendering the badge. This is both safer and more idiomatic Svelte.SOP COMPLIANCE
37-wire-components-to-data)PROCESS OBSERVATIONS
mergeContractConfigsince it now handles three new field propagations.static_contentnewline convention should be documented somewhere (inline comment or a README note) so the next developer knows what format the database stores.VERDICT: NOT APPROVED
Reason: New merge logic (
variant,static_contentpropagation inmergeContractConfig) has zero test coverage. Add unit tests for the merge function covering: (1) variant propagation, (2) static_content propagation, (3) behavior when these fields are absent from config. The XSS/HTML injection pattern in PracticeSchedule rows should also be addressed or documented as a known limitation before merge.Blocker addressed in commit
0b3d70a:Added 8 tests covering
mergeContractConfigpropagation ofvariant,static_content, extended tournament fields (dates,cost,local,travel,entry_fee), practiceoptionalflag, and paymentlabel/datefields. Test suite now at 38/38 passing.Nit responses:
<span>is hardcoded in the template expression (not user input), so no XSS vector. Same pattern as the existing fallback code.due_date/amount/description) and new (label/date) schemas during migration.\n\nsplitting -- this is standard paragraph splitting for text stored with literal newlines in JSONB. Correct for the database format.Ready for re-review.