refactor(sdk-app): split design components into View/Container layers#1986
refactor(sdk-app): split design components into View/Container layers#1986aaronlee777 wants to merge 18 commits into
Conversation
Adds a read-only CompensationHistory component that lists every job with its full effective-dated compensation history (effective date, employee type, wage + frequency). Surfaced via a new tertiary "View history" button in the Job & pay compensation card, wired through the dashboard state machine alongside AddAnotherJob and EditCompensation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…able single view - Render a single combined DataView (with Job title column) when the employee has 2+ jobs, sorted by effective date descending across jobs. - Add a Job filter Select beside the heading that defaults to "All jobs" and narrows rows to the chosen job. - Keep the per-job heading + 3-column DataView when there's only one job. - Show an empty state when the employee has no jobs. - Use useContainerBreakpoints + FlexItem to stack the heading and Select in narrow containers (matches the PayrollConfiguration header pattern). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
FlexItem with no props is a div with flex-grow: initial, which is the default for any flex child — the wrappers were not load-bearing. Heading and the .jobFilter div behave identically as direct Flex children. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the read-only CompensationHistory component out of the SDK surface and into sdk-app/src/design/prototypes/employee-management/CompensationHistory. Strings are hardcoded (no i18n dependency) since prototypes don't use the SDK's translation system. Revert all SDK wiring back to main: - Remove CompensationHistoryContextual from DashboardComponents - Remove compensationHistory state + EMPLOYEE_COMPENSATION_HISTORY_VIEW transition from dashboardStateMachine - Drop the "View history" tertiary button from JobAndPayView - Remove handleViewCompensationHistory wiring from Dashboard - Remove the export from Compensation/management/index.ts - Remove the EMPLOYEE_COMPENSATION_HISTORY_VIEW event constant - Remove the history.* translation keys and viewHistoryCta Wire the prototype into the design app, matching the existing contractor-management folder/index.tsx convention: - index.tsx reads employeeId from outlet context (optional prop kept for testability) - New route /design/employee-compensation-history in sdk-app main router - Register the prototype under the Employees category in design/registry Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a Storybook-lite viewer for browsing prototype component states with mocked data, alongside the existing live-data prototype view. Each prototype declares its own `states.tsx` listing components and configurations (slug, name, MSW handlers, render fn). The new ComponentStatesPage renders a right-rail sidebar of those configurations and uses MSW (browser worker) to intercept SDK API calls so each configuration renders with deterministic mock data. The browser worker lazy-starts on first selection and uses `onUnhandledRequest: 'bypass'` so live routes are unaffected. CompensationHistory is the first migrated prototype. Its sidebar entry now nests two children — Prototype (live) and Component states — and the latter exposes Single job, Multiple jobs, Mixed wages, and Empty configurations built from the shared `buildJob` factory primitive. Empty states render via DataView's emptyState prop instead of bare text. Misc: - DesignLayout becomes a flex shell with a portal-based right rail so the component-states sidebar can sit flush against the viewport edge. - Left sidebar: tighter active treatment (color only, no border/bg), uppercase tiny category labels, and a font-weight bump on top-level items so nested children read as a clear visual hierarchy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reorganize the design app so design primitives are clearly separated
from prototype wiring. Establishes a pattern any future prototype work
can follow.
New structure:
sdk-app/src/design/
components/ Views — domain-organized presentational
common/ primitives. Anywhere can import.
Skeleton.tsx
contractorName.ts
contractor/
ContractorList/
ContractorList.tsx (View)
ContractorListStates.tsx (state-demo helper)
SkeletonDataView.tsx
ContractorDismissalForm/
ContractorDismissalForm.tsx
ContractorDismissalFormStates.tsx
prototypes/ Containers + route assemblies.
contractor-management/
ContractorListRoute.tsx
ContractorDismissRoute.tsx
states.tsx (manifest of which component
states show under this proto)
employee-management/
CompensationHistory/
Naming convention:
- View -> bare name (ContractorList)
- Route/Container -> suffix Route (ContractorListRoute)
- State demos -> suffix States (ContractorListStates)
What this work shipped along the way:
- A right-rail Component States viewer per prototype, fed by MSW or by
passing mock data directly to a View
- CompensationHistory as the first View-only prototype
- ContractorList split into View + Container, with mock data passed to
the View directly (no MSW needed for state demos)
- ContractorDismissalForm extracted as a stand-alone View with its own
state demos
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reorganize design/components/contractor/ into a management/ subfolder so future onboarding-specific contractor Views can live alongside as contractor/onboarding/ without colliding. Before: design/components/contractor/<Name>/ After: design/components/contractor/management/<Name>/ All 15 contractor Views moved, internal common-imports updated to the new depth, and every external consumer's import path patched to inject management/ after components/contractor/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Same shape as ContractorDismissRoute and ContractorListRoute: move the container out of contractor-list/ContractorRehire/index.tsx to a flat ContractorRehireRoute.tsx alongside its siblings. Rename the export to ContractorRehireRoute and update main.tsx accordingly. The View (ContractorRehireForm) was already in design/components/contractor/management/ from earlier rounds. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ctor - Move AddressForm, PaymentMethodForm, ProfileDetailsForm from design/components/contractor/management/ to .../contractor/shared/ so they can serve both management and self-onboarding consumers without implying a management-only home. - Flatten AddContractor wizard from prototypes/contractor-management/contractor-list/AddContractor/ to prototypes/contractor-management/AddContractor/, matching the flat layout used by the other prototype routes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Lift self-onboarding to its own sibling prototype at prototypes/contractor-self-onboarding/ (it was nested under contractor-management/ for historical reasons, but is a separate contractor-facing experience with its own /design route). - Flatten contractor-profile/ subfolder: ContractorManagementFlow (the outer routing shell for the whole prototype) becomes a flat file, and ContractorProfile/ moves up to live alongside the other route containers. - Drop the Route suffix from single-file route containers (ContractorListRoute -> ContractorList, etc.) so the naming rule is consistent: single file when one file, folder when more, no suffix in either case. Alias the ContractorList View on import in the container to avoid the local name collision. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The shared PaymentMethodForm was inconsistent with its siblings in shared/ — AddressForm and ProfileDetailsForm are pure Views with no data fetching, but PaymentMethodForm did its own suspense queries, mutations, and event dispatching. That made it impossible to render in a component-states preview and welded the View to the API layer. - shared/PaymentMethodForm/PaymentMethodForm.tsx is now a pure View: takes defaultValues, isPending, onSubmit. Internally runs zod validation plus the lazy accountNumber regex check (only when the user actually changed the value vs. the masked default). - Self-onboarding container (ContractorSelfPaymentMethodForm) and AddContractor wizard step (ContractorPaymentMethod) now own the fetch + mutate + event-dispatch wiring, matching the AddressForm / ProfileDetailsForm pattern. - Fix stale field name: createBankAccount request uses contractorBankAccountCreateRequestBody, not requestBody (the previous shared form was using the old field name and only worked because the type checker had not flagged it). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…history # Conflicts: # sdk-app/src/Sidebar.tsx # sdk-app/src/design/components/contractor/management/ContractorAddressForm/ContractorAddressForm.tsx # sdk-app/src/design/components/contractor/management/ContractorDetailsForm/ContractorDetailsForm.tsx # sdk-app/src/design/components/contractor/management/ContractorList/ContractorList.tsx # sdk-app/src/design/components/contractor/management/ContractorPaymentMethod/ContractorPaymentMethod.tsx # sdk-app/src/design/prototypes/contractor-management/AddContractor/ContractorAddress.tsx # sdk-app/src/design/prototypes/contractor-management/ContractorDismiss.tsx # sdk-app/src/design/prototypes/contractor-management/ContractorRehire.tsx # sdk-app/src/design/prototypes/contractor-management/common/PaymentMethodForm.tsx # sdk-app/src/design/prototypes/contractor-self-onboarding/ContractorSelfAddressForm.tsx # sdk-app/src/design/prototypes/contractor-self-onboarding/ContractorSelfProfile.tsx
Apply the View/Container pattern (shared with contractor-management) to the CompensationHistory prototype. - New pure View at design/components/employee/management/CompensationHistory/. Takes jobs as a prop. Owns the single-vs-combined branching, the local job filter state, and the container-breakpoints responsive header. - Container at prototypes/employee-management/CompensationHistory/CompensationHistory.tsx (renamed from CompensationHistoryComponent — drop the Component suffix to match the contractor pattern). Fetches via useJobsAndCompensationsGetJobs, wraps the View with BaseBoundaries + BaseLayout. Imports the View as CompensationHistoryView to disambiguate. - states.tsx now renders the View directly with camelCase Job mocks — no MSW handlers needed. - Test moves next to the View and exercises the View by passing jobs as a prop, dropping the MSW + buildEmployeeWithJobs setup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Now that every state demo renders a pure View directly with mock props, the MSW-based mock-data infrastructure no longer pulls its weight. Deleted: - sdk-app/src/design/prototypes/MockedRender.tsx (replaced by an inline configuration.render() call in ComponentStatesPage — the nested GustoProvider it set up to point at https://api.gusto.com/ was only there for MSW to intercept, which no current demo needs) - sdk-app/src/design/prototypes/MockedEntitiesOutlet.tsx (no View reads outlet entities; Views take data via props) - sdk-app/src/mocks/browser.ts + useMockHandlers.ts (only consumer was MockedRender) - sdk-app/public/mockServiceWorker.js (gitignored, removed from disk) - The `handlers: RequestHandler[]` field on PrototypeConfiguration and every `handlers: []` line in the contractor + compensation-history states files main.tsx no longer wraps the contractor-management component-states route in MockedEntitiesOutlet. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ding Apply the View/Container split to the remaining mixed self-onboarding files and wire up a component-states viewer so each screen can be previewed in isolation with mock data. Views extracted into design/components/contractor/self-onboarding/: - ContractorWelcome (from ContractorLanding) — takes contractorName, companyName, onStart - ContractorDocumentList (from ContractorDocumentSigner) — takes documents, onRequestSign, onContinue - ContractorSignaturePad (from ContractorSignatureForm) — takes title, description, pdfUrl, isPending, onSubmit, onBack - ContractorOnboardingComplete (from ContractorOnboardingSummary, which was already pure presentation) — takes onDone Containers in prototypes/contractor-self-onboarding/ slimmed to fetch data + dispatch events + render the View. New states viewer at /design/contractor-self-onboarding/component-states/ listing all four self-onboarding Views plus the three shared forms (Profile Details, Address, Payment Method) with onboarding-context copy. The same three shared forms are also added to the contractor-management states viewer with management-context copy so both flows surface them. registry.ts gains Prototype / Component states children under Contractor Self-Onboarding to expose the new route in the sidebar. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
⚠️ 1 New Security Finding
The latest commit contains 1 new security finding.
Findings Note: 1 finding is displayed as an inline comment.
Not a finding? Ignore it by adding a comment on the line with just the word noboost.
Scanner: boostsecurity - Semgrep
|
|
||
| function buildDocument(overrides: Partial<Document>): Document { | ||
| return { | ||
| uuid: `doc-${Math.random().toString(36).slice(2, 8)}`, |
There was a problem hiding this comment.
CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG)
Original Rule ID: rules_lgpl_javascript_crypto_rule-node-insecure-random-generator
Details
The product uses a Pseudo-Random Number Generator (PRNG) in a security context, but the PRNG's algorithm is not cryptographically strong.
The application uses cryptographically weak random number generators such as
crypto.pseudoRandomBytes() or Math.random() for security-sensitiveoperations. The
crypto.pseudoRandomBytes() function uses a non-cryptographicPRNG that may be predictable, while
Math.random() is explicitly designed forgeneral-purpose use and is completely unsuitable for cryptographic purposes.
Using these functions for generating session tokens, CSRF tokens, password reset
tokens, encryption keys, or other security-critical values allows attackers to
predict future outputs by observing previous values. This predictability can lead
to session hijacking, authentication bypass, or complete compromise of
cryptographic protections.
📘 Learn More
AI Remediation
The code previously used Math.random() to generate part of a document UUID, which is not cryptographically secure and could allow an attacker to predict values. The fix replaces it with crypto.randomBytes(), which uses the operating system's secure random number generator to provide unpredictability and improved security for generated identifiers.
| uuid: `doc-${Math.random().toString(36).slice(2, 8)}`, | |
| uuid: `doc-${require('crypto').randomBytes(4).toString('hex')}`, |
…Signature The "SignaturePad" name implied a drawing canvas, but the View is just a "type your full legal name + agree checkbox" form. Renaming to ContractorDocumentSignature so the name reflects what it actually does. Also point the demo configurations at a real W-9 PDF (https://www.irs.gov/pub/irs-pdf/fw9.pdf) instead of passing pdfUrl=null, so the DocumentViewer actually renders a usable View document link in the state-viewer preview. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
irs.gov refuses to be embedded (sets X-Frame-Options or equivalent), so the DocumentViewer's <embed> tag was failing with a "refused to connect" error in the component-states preview. Bundle the W-9 PDF at sdk-app/public/sample-documents/w9.pdf and point the demo configurations at the relative path so the preview actually renders. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Split the design app's prototype layer into pure Views (presentational, prop-driven) and thin Containers (data fetching + event dispatch + route wiring). Once that split exists, a per-prototype Component States viewer can render any View in isolation with mock data — no MSW, no live API.
Migrated CompensationHistory, all of contractor-management, and all of contractor-self-onboarding to the new pattern. Three prototype state viewers ship as part of this change.
Final layout
Naming rule: single file per route container when one file is enough; folder when multiple. No
Routesuffix anywhere — the folder location already says it. Views and containers can share a name (e.g.ContractorList); when both end up in the same file the View is aliased on import (as ContractorListView).What this PR delivers
Structure
design/components/{contractor,employee}/{management,self-onboarding,shared}/.design/prototypes/<prototype>/that own data fetching, mutations, and event dispatch.self-onboarding/lifted from insidecontractor-management/to a sibling prototype (prototypes/contractor-self-onboarding/).contractor-profile/subfolder flattened —ContractorManagementFlowandContractorProfile/now sit at the prototype root.Routesuffix dropped fromContractorList,ContractorDismiss,ContractorRehire.AddContractor/wizard flattened toprototypes/contractor-management/AddContractor/.Component States viewers
Three live viewers, each with a right-rail sidebar that lists Views and per-View configurations:
/design/employee-compensation-history/component-states/— CompensationHistory (4 configs)./design/contractor-management/component-states/— ContractorList (8 configs), ContractorDismissalForm (4), ContractorDetails (4), ProfileDetailsForm (2), AddressForm (2), PaymentMethodForm (2)./design/contractor-self-onboarding/component-states/— ContractorWelcome (3), ContractorDocumentList (4), ContractorDocumentSignature (2), ContractorOnboardingComplete (1), ProfileDetailsForm (3), AddressForm (2), PaymentMethodForm (3).Shared-form viewers in both contractor surfaces so the same Views can be inspected with management-context copy or onboarding-context copy.
Shared form refactor
PaymentMethodForm(shared) was a container masquerading as a View — it did its own suspense queries, mutations, and event dispatch. Split into a pure View (just fields + form mechanics) and lifted the data wiring into each consumer (self-onboarding container + AddContractor wizard step). MatchesAddressForm/ProfileDetailsFormshape. Also fixed a stalerequestBodyfield name (the SDK type expectscontractorBankAccountCreateRequestBody).Dead-code removal
MockedRender,MockedEntitiesOutlet,sdk-app/src/mocks/(browser worker + useMockHandlers), andsdk-app/public/mockServiceWorker.js. With every state demo passing data via props directly, the MSW machinery wasn't earning its keep.handlers: RequestHandler[]field fromPrototypeConfigurationand everyhandlers: []line in states files.ComponentStatesPagenow callsconfiguration.render()directly.Other small wins
sdk-app/public/sample-documents/w9.pdfso the DocumentSignature demo'sDocumentVieweractually renders something (irs.gov refuses to be embedded).What's not in this PR
AddContractor/ContractorProfile.tsxstep (with the self-onboarding toggle + classification fields) is intentionally kept separate from the sharedProfileDetailsForm. The forms are structurally different and consolidating would mean ~8 toggle props on the shared View.Test plan
npm run sdk-app— open the design app./design/employee-compensation-history/prototype,/design/contractor-management,/design/contractor-self-onboarding.npx tsc --noEmitclean.npm run test -- --run sdk-app/src/design/components/employee/management/CompensationHistory/CompensationHistory.test.tsxpasses (4 tests).🤖 Generated with Claude Code