From e9dab04b343dfa28e9b3f5fa8ce543cd586c1682 Mon Sep 17 00:00:00 2001 From: adilallo <39313955+adilallo@users.noreply.github.com> Date: Sat, 18 Apr 2026 14:12:49 -0600 Subject: [PATCH] App reorganization --- .cursor/rules/component-props.mdc | 68 +- .cursor/rules/component-structure.mdc | 31 +- .cursor/rules/create-flow.mdc | 12 +- .cursor/rules/hooks.mdc | 59 + .cursor/rules/routes.mdc | 88 ++ .cursor/rules/storybook.mdc | 29 +- AGENTS.md | 82 ++ CONTRIBUTING.md | 91 +- README.md | 272 +---- app/(admin)/layout.tsx | 7 + app/(admin)/monitor/page.tsx | 2 +- .../create/CreateFlowLayoutClient.tsx | 26 +- .../create/CreateFlowLayoutGate.tsx | 0 .../create/PostLoginDraftTransfer.tsx | 4 +- .../create/SignedInDraftHydration.tsx | 6 +- app/{ => (app)}/create/[screenId]/page.tsx | 0 .../components/ApplicableScopeField.tsx | 10 +- .../components/CreateFlowHeaderLockup.tsx | 4 +- .../CreateFlowLockupCardStepShell.tsx | 0 .../create/components/CreateFlowStepShell.tsx | 0 .../CreateFlowTwoColumnSelectShell.tsx | 0 .../create/components/ModalTextAreaField.tsx | 4 +- .../components/createFlowLayoutTokens.ts | 0 .../create/context/CreateFlowContext.tsx | 0 .../CreateFlowDraftSaveBannerContext.tsx | 0 .../create/hooks/useCreateFlowExit.ts | 4 +- .../create/hooks/useCreateFlowLgUp.ts | 2 +- .../create/hooks/useCreateFlowMdUp.ts | 2 +- .../create/hooks/useCreateFlowNavigation.ts | 0 app/{ => (app)}/create/layout.tsx | 0 app/{ => (app)}/create/page.tsx | 0 .../create/review-template/[slug]/page.tsx | 10 +- .../create/screens/CreateFlowScreenView.tsx | 0 .../card/CommunicationMethodsScreen.tsx | 8 +- .../screens/card/ConflictManagementScreen.tsx | 8 +- .../screens/card/MembershipMethodsScreen.tsx | 8 +- .../screens/completed/CompletedScreen.tsx | 12 +- .../informational/InformationalScreen.tsx | 4 +- .../screens/review/CommunityReviewScreen.tsx | 4 +- .../screens/review/FinalReviewScreen.tsx | 6 +- .../right-rail/DecisionApproachesScreen.tsx | 16 +- .../select/CommunitySizeSelectScreen.tsx | 26 +- .../select/CommunityStructureSelectScreen.tsx | 50 +- .../select/ConfirmStakeholdersScreen.tsx | 14 +- .../screens/select/CoreValuesSelectScreen.tsx | 44 +- .../text/CreateFlowTextFieldScreen.tsx | 6 +- .../screens/upload/CommunityUploadScreen.tsx | 4 +- app/{ => (app)}/create/types.ts | 0 .../create/utils/anonymousDraftStorage.ts | 11 +- .../utils/coreValueDetailsLocalStorage.ts | 0 .../utils/createFlowProportionProgress.ts | 2 +- .../create/utils/createFlowScreenRegistry.ts | 6 +- app/{ => (app)}/create/utils/flowSteps.ts | 0 .../create/utils/hasCreateFlowUserInput.ts | 0 app/(app)/layout.tsx | 8 + app/{ => (app)}/login/layout.tsx | 0 app/{ => (app)}/login/page.tsx | 9 +- app/{ => (app)}/profile/ProfilePageClient.tsx | 6 +- app/{ => (app)}/profile/page.tsx | 0 app/(dev)/components-preview/page.tsx | 207 ++-- app/(dev)/layout.tsx | 7 + .../MarketingRuleStackSection.tsx | 8 +- app/(marketing)/layout.tsx | 21 + app/(marketing)/page.tsx | 2 +- app/components/asset/{index.ts => index.tsx} | 0 .../asset/logo/{index.ts => index.tsx} | 0 app/components/buttons/Button.tsx | 25 +- app/components/cards/NumberCard.tsx | 61 +- .../cards/RuleCard/RuleCard.container.tsx | 4 +- .../cards/RuleCard/RuleCard.types.ts | 2 +- .../cards/RuleCard/RuleCard.view.tsx | 4 +- .../{index.ts => index.tsx} | 0 .../ContentContainer.container.tsx | 4 +- .../ContentContainer.types.ts | 9 +- .../ContentThumbnailTemplate.container.tsx | 4 +- .../ContentThumbnailTemplate.types.ts | 9 +- .../controls/Checkbox/Checkbox.container.tsx | 13 +- .../controls/Checkbox/Checkbox.types.ts | 10 +- .../CheckboxGroup/CheckboxGroup.container.tsx | 8 +- .../CheckboxGroup/CheckboxGroup.types.ts | 3 +- .../controls/Chip/Chip.container.tsx | 21 +- app/components/controls/Chip/Chip.types.ts | 32 +- .../InputWithCounter.container.tsx | 18 + .../controls/InputWithCounter/index.tsx | 2 +- .../MultiSelect/MultiSelect.container.tsx | 16 +- .../controls/MultiSelect/MultiSelect.types.ts | 8 +- .../controls/MultiSelect/MultiSelect.view.tsx | 15 +- .../RadioButton/RadioButton.container.tsx | 9 +- .../controls/RadioButton/RadioButton.types.ts | 6 +- .../RadioGroup/RadioGroup.container.tsx | 21 +- .../controls/RadioGroup/RadioGroup.types.ts | 6 +- .../SelectInput/SelectInput.container.tsx | 25 +- .../controls/SelectInput/SelectInput.types.ts | 23 +- .../controls/SelectInput/SelectInput.view.tsx | 2 +- .../SelectOption/SelectOption.container.tsx | 8 +- .../SelectOption/SelectOption.types.ts | 11 +- .../SelectOption/SelectOption.view.tsx | 0 .../{SelectInput => }/SelectOption/index.tsx | 0 .../controls/Switch/Switch.container.tsx | 8 +- .../controls/Switch/Switch.types.ts | 3 +- .../controls/TextArea/TextArea.container.tsx | 19 +- .../controls/TextArea/TextArea.types.ts | 29 +- .../TextInput/TextInput.container.tsx | 13 +- .../controls/TextInput/TextInput.types.ts | 8 +- .../controls/Toggle/Toggle.container.tsx | 8 +- .../controls/Toggle/Toggle.types.ts | 3 +- .../ToggleGroup/ToggleGroup.container.tsx | 13 +- .../controls/ToggleGroup/ToggleGroup.types.ts | 16 +- .../controls/Upload/Upload.container.tsx | 4 + .../controls/Upload/Upload.view.tsx | 4 +- app/components/icons/Avatar.tsx | 18 +- .../LanguageSwitcher/{index.ts => index.tsx} | 0 .../modals/Alert/Alert.container.tsx | 9 +- app/components/modals/Alert/Alert.types.ts | 18 +- .../{ => modals}/ContextMenu/ContextMenu.tsx | 0 .../ContextMenu/ContextMenuDivider.tsx | 0 .../ContextMenu/ContextMenuSection.tsx | 0 .../ContextMenuItem.container.tsx | 4 +- .../ContextMenuItem/ContextMenuItem.types.ts | 11 +- .../ContextMenuItem/ContextMenuItem.view.tsx | 0 .../ContextMenuItem/index.tsx | 0 app/components/modals/Login/LoginForm.tsx | 2 +- .../modals/Tooltip/Tooltip.container.tsx | 4 +- .../modals/Tooltip/Tooltip.types.ts | 5 +- .../navigation/ConditionalFooter.tsx | 33 - app/components/navigation/MenuBar.tsx | 3 +- .../MenuBarItem/MenuBarItem.container.tsx | 11 +- .../NavigationItem.container.tsx | 9 +- .../NavigationItem/NavigationItem.types.ts | 14 +- .../ProportionBar/ProportionBar.container.tsx | 3 +- .../ProportionBar/ProportionBar.types.ts | 4 +- .../AskOrganizer/AskOrganizer.container.tsx | 6 +- .../AskOrganizer/AskOrganizer.types.ts | 9 +- .../{index.ts => index.tsx} | 0 .../QuoteBlock/QuoteBlock.container.tsx | 4 +- .../sections/QuoteBlock/QuoteBlock.types.ts | 11 +- app/components/sections/SectionHeader.tsx | 14 +- .../WebVitalsDashboard.container.tsx | 2 +- .../WebVitalsDashboard.types.ts | 0 .../WebVitalsDashboard.view.tsx | 0 .../WebVitalsDashboard/index.tsx | 0 .../ContentLockup/ContentLockup.container.tsx | 9 +- .../type/ContentLockup/ContentLockup.types.ts | 17 +- .../HeaderLockup/HeaderLockup.container.tsx | 12 +- .../type/HeaderLockup/HeaderLockup.types.ts | 23 +- .../NumberedList/NumberedList.container.tsx | 4 +- .../type/NumberedList/NumberedList.types.ts | 5 +- .../{ => AvatarContainer}/AvatarContainer.tsx | 18 +- .../utility/AvatarContainer/index.tsx | 2 + .../utility/CardStack/CardStack.container.tsx | 4 + .../CreateFlowFooter.container.tsx | 4 + .../CreateFlowFooter.view.tsx | 5 +- .../CreateFlowTopNav.container.tsx | 4 + .../DecisionMakingSidebar.container.tsx | 12 +- app/components/utility/ErrorBoundary.tsx | 58 - app/components/utility/ImagePlaceholder.tsx | 70 -- .../InfoMessageBox.container.tsx | 4 + .../InputLabel/InputLabel.container.tsx | 16 +- .../utility/InputLabel/InputLabel.types.ts | 14 +- .../ModalFooter/ModalFooter.container.tsx | 18 + app/components/utility/ModalFooter/index.tsx | 2 +- .../ModalHeader/ModalHeader.container.tsx | 18 + app/components/utility/ModalHeader/index.tsx | 2 +- .../utility/Scrollbar/Scrollbar.container.tsx | 18 + app/components/utility/Scrollbar/index.tsx | 2 +- .../utility/{ => Separator}/Separator.tsx | 0 app/components/utility/Separator/index.tsx | 1 + app/components/utility/Tag/Tag.container.tsx | 4 + app/contexts/MessagesContext.tsx | 30 +- app/hooks/index.ts | 12 - app/hooks/useComponentStyles.ts | 109 -- app/hooks/useFormValidation.ts | 213 ---- app/layout.tsx | 4 +- docs/CUSTOM_HOOKS.md | 381 ------ docs/README.md | 93 +- docs/TESTING_GUIDE.md | 266 ----- docs/create-flow.md | 30 +- docs/{ => guides}/backend-linear-tickets.md | 48 +- docs/{ => guides}/backend-roadmap.md | 6 +- docs/guides/container-presentation-pattern.md | 401 ------- docs/guides/i18n-translation-workflow.md | 411 +------ .../template-recommendation-matrix.md | 30 +- docs/testing-guide.md | 68 ++ lib/cache.ts | 266 ----- lib/create/api.ts | 2 +- lib/create/buildPublishPayload.ts | 2 +- lib/create/draftHydrationUtils.ts | 2 +- lib/create/migrateLegacyCreateFlowState.ts | 2 +- lib/i18n/getTranslation.ts | 28 - lib/i18n/types.ts | 21 - lib/mdx.ts | 354 ------ lib/propNormalization.ts | 1033 ++++------------- lib/server/validation/createFlowSchemas.ts | 2 +- lib/types.ts | 29 - messages/en/pages/login.json | 1 + performance-budgets.json | 22 +- scripts/test-local.sh | 92 -- stories/ContextMenu/ContextMenu.stories.js | 8 +- stories/WebVitalsDashboard.stories.js | 2 +- stories/asset/Icon.stories.js | 42 + stories/cards/NumberCard.stories.js | 8 +- stories/cards/RuleCard.stories.js | 52 +- stories/cards/TemplateReviewCard.stories.js | 66 ++ stories/controls/Checkbox.stories.js | 20 +- stories/controls/Chip.stories.js | 83 ++ stories/controls/InputWithCounter.stories.js | 82 ++ stories/controls/MultiSelect.stories.js | 85 ++ stories/controls/RadioButton.stories.js | 29 +- .../ApplicableScopeField.stories.js | 2 +- .../create-flow/ModalTextAreaField.stories.js | 2 +- .../localization/LanguageSwitcher.stories.js | 19 + stories/modals/Login.stories.tsx | 2 +- stories/navigation/NavigationItem.stories.js | 75 ++ .../pages/CommunicationMethodsPage.stories.js | 2 +- stories/pages/CompletedPage.stories.js | 2 +- .../pages/ConfirmStakeholdersPage.stories.js | 2 +- .../pages/DecisionApproachesPage.stories.js | 2 +- stories/pages/FinalReviewPage.stories.js | 2 +- stories/pages/InformationalPage.stories.js | 2 +- stories/pages/ReviewPage.stories.js | 2 +- stories/pages/SelectPage.stories.js | 2 +- stories/pages/TextPage.stories.js | 2 +- stories/pages/UploadPage.stories.js | 2 +- stories/progress/ProportionBar.stories.js | 2 +- .../sections/CommunityRuleDocument.stories.js | 58 + .../GovernanceTemplateGrid.stories.js | 29 + stories/utility/ErrorBoundary.stories.js | 40 - stories/utility/ModalFooter.stories.js | 71 ++ stories/utility/ModalHeader.stories.js | 42 + stories/utility/Separator.stories.js | 28 + .../components/ApplicableScopeField.test.tsx | 2 +- tests/components/Button.test.tsx | 1 + tests/components/Chip.test.tsx | 33 + .../components/CommunityRuleDocument.test.tsx | 37 + tests/components/CompletedPage.test.tsx | 2 +- .../ConfirmStakeholdersPage.test.tsx | 2 +- tests/components/ContentBanner.test.tsx | 1 + tests/components/ContextMenu.test.tsx | 4 +- tests/components/ContextMenuItem.test.tsx | 2 +- .../CoreValuesSelectScreen.test.tsx | 2 +- tests/components/CreateFlowFooter.test.tsx | 1 + tests/components/FinalReviewPage.test.tsx | 4 +- .../GovernanceTemplateGrid.test.tsx | 28 + tests/components/HeaderLockup.test.tsx | 5 +- tests/components/Icon.test.tsx | 26 + tests/components/IconCard.test.tsx | 1 + tests/components/InformationalPage.test.tsx | 2 +- tests/components/InputLabel.test.tsx | 24 +- tests/components/InputWithCounter.test.tsx | 31 + tests/components/LanguageSwitcher.test.tsx | 23 + tests/components/LoginForm.test.tsx | 6 +- tests/components/Logo.test.tsx | 1 + tests/components/ModalFooter.test.tsx | 30 + tests/components/ModalHeader.test.tsx | 28 + tests/components/ModalTextAreaField.test.tsx | 2 +- tests/components/MultiSelect.test.tsx | 16 +- tests/components/NavigationItem.test.tsx | 29 + tests/components/NumberedList.test.tsx | 1 + tests/components/ProportionBar.test.tsx | 1 + tests/components/RelatedArticles.test.tsx | 1 + tests/components/ReviewPage.test.tsx | 2 +- tests/components/SelectPage.test.tsx | 2 +- tests/components/Separator.test.tsx | 22 + tests/components/Stepper.test.tsx | 1 + tests/components/TemplateReviewCard.test.tsx | 52 + tests/components/TextInput.test.tsx | 6 +- tests/components/TextPage.test.tsx | 2 +- tests/components/Tooltip.test.tsx | 1 + tests/components/Upload.test.tsx | 1 + tests/components/UploadPage.test.tsx | 2 +- .../AuthModalContext.test.tsx | 6 +- tests/pages/communication-methods.test.jsx | 2 +- tests/pages/decision-approaches.test.jsx | 2 +- tests/unit/ContentContainer.test.jsx | 1 + tests/unit/ContentThumbnailTemplate.test.jsx | 1 + tests/unit/Layout.test.jsx | 56 +- tests/unit/LogoWall.test.jsx | 1 + tests/unit/NumberCard.test.jsx | 9 +- tests/unit/RuleCard.test.jsx | 2 +- tests/unit/buildPublishPayload.test.ts | 6 +- tests/unit/createFlowLayoutTokens.test.ts | 2 +- .../unit/createFlowProportionProgress.test.ts | 2 +- tests/unit/createFlowValidation.test.ts | 4 +- tests/unit/flowSteps.test.ts | 2 +- tests/unit/hasCreateFlowUserInput.test.ts | 2 +- tests/unit/saveDraftToServer.test.ts | 2 +- tests/utils/test-utils.tsx | 2 +- vitest.config.mjs | 6 +- 288 files changed, 2698 insertions(+), 5029 deletions(-) create mode 100644 .cursor/rules/hooks.mdc create mode 100644 .cursor/rules/routes.mdc create mode 100644 AGENTS.md create mode 100644 app/(admin)/layout.tsx rename app/{ => (app)}/create/CreateFlowLayoutClient.tsx (96%) rename app/{ => (app)}/create/CreateFlowLayoutGate.tsx (100%) rename app/{ => (app)}/create/PostLoginDraftTransfer.tsx (97%) rename app/{ => (app)}/create/SignedInDraftHydration.tsx (95%) rename app/{ => (app)}/create/[screenId]/page.tsx (100%) rename app/{ => (app)}/create/components/ApplicableScopeField.tsx (95%) rename app/{ => (app)}/create/components/CreateFlowHeaderLockup.tsx (79%) rename app/{ => (app)}/create/components/CreateFlowLockupCardStepShell.tsx (100%) rename app/{ => (app)}/create/components/CreateFlowStepShell.tsx (100%) rename app/{ => (app)}/create/components/CreateFlowTwoColumnSelectShell.tsx (100%) rename app/{ => (app)}/create/components/ModalTextAreaField.tsx (93%) rename app/{ => (app)}/create/components/createFlowLayoutTokens.ts (100%) rename app/{ => (app)}/create/context/CreateFlowContext.tsx (100%) rename app/{ => (app)}/create/context/CreateFlowDraftSaveBannerContext.tsx (100%) rename app/{ => (app)}/create/hooks/useCreateFlowExit.ts (93%) rename app/{ => (app)}/create/hooks/useCreateFlowLgUp.ts (91%) rename app/{ => (app)}/create/hooks/useCreateFlowMdUp.ts (91%) rename app/{ => (app)}/create/hooks/useCreateFlowNavigation.ts (100%) rename app/{ => (app)}/create/layout.tsx (100%) rename app/{ => (app)}/create/page.tsx (100%) rename app/{ => (app)}/create/review-template/[slug]/page.tsx (91%) rename app/{ => (app)}/create/screens/CreateFlowScreenView.tsx (100%) rename app/{ => (app)}/create/screens/card/CommunicationMethodsScreen.tsx (96%) rename app/{ => (app)}/create/screens/card/ConflictManagementScreen.tsx (97%) rename app/{ => (app)}/create/screens/card/MembershipMethodsScreen.tsx (96%) rename app/{ => (app)}/create/screens/completed/CompletedScreen.tsx (87%) rename app/{ => (app)}/create/screens/informational/InformationalScreen.tsx (93%) rename app/{ => (app)}/create/screens/review/CommunityReviewScreen.tsx (94%) rename app/{ => (app)}/create/screens/review/FinalReviewScreen.tsx (89%) rename app/{ => (app)}/create/screens/right-rail/DecisionApproachesScreen.tsx (93%) rename app/{ => (app)}/create/screens/select/CommunitySizeSelectScreen.tsx (79%) rename app/{ => (app)}/create/screens/select/CommunityStructureSelectScreen.tsx (89%) rename app/{ => (app)}/create/screens/select/ConfirmStakeholdersScreen.tsx (87%) rename app/{ => (app)}/create/screens/select/CoreValuesSelectScreen.tsx (90%) rename app/{ => (app)}/create/screens/text/CreateFlowTextFieldScreen.tsx (93%) rename app/{ => (app)}/create/screens/upload/CommunityUploadScreen.tsx (91%) rename app/{ => (app)}/create/types.ts (100%) rename app/{ => (app)}/create/utils/anonymousDraftStorage.ts (89%) rename app/{ => (app)}/create/utils/coreValueDetailsLocalStorage.ts (100%) rename app/{ => (app)}/create/utils/createFlowProportionProgress.ts (93%) rename app/{ => (app)}/create/utils/createFlowScreenRegistry.ts (95%) rename app/{ => (app)}/create/utils/flowSteps.ts (100%) rename app/{ => (app)}/create/utils/hasCreateFlowUserInput.ts (100%) create mode 100644 app/(app)/layout.tsx rename app/{ => (app)}/login/layout.tsx (100%) rename app/{ => (app)}/login/page.tsx (85%) rename app/{ => (app)}/profile/ProfilePageClient.tsx (88%) rename app/{ => (app)}/profile/page.tsx (100%) create mode 100644 app/(dev)/layout.tsx rename app/(marketing)/{ => _components}/MarketingRuleStackSection.tsx (66%) create mode 100644 app/(marketing)/layout.tsx rename app/components/asset/{index.ts => index.tsx} (100%) rename app/components/asset/logo/{index.ts => index.tsx} (100%) rename app/components/cards/TemplateReviewCard/{index.ts => index.tsx} (100%) create mode 100644 app/components/controls/InputWithCounter/InputWithCounter.container.tsx rename app/components/controls/{SelectInput => }/SelectOption/SelectOption.container.tsx (91%) rename app/components/controls/{SelectInput => }/SelectOption/SelectOption.types.ts (68%) rename app/components/controls/{SelectInput => }/SelectOption/SelectOption.view.tsx (100%) rename app/components/controls/{SelectInput => }/SelectOption/index.tsx (100%) rename app/components/localization/LanguageSwitcher/{index.ts => index.tsx} (100%) rename app/components/{ => modals}/ContextMenu/ContextMenu.tsx (100%) rename app/components/{ => modals}/ContextMenu/ContextMenuDivider.tsx (100%) rename app/components/{ => modals}/ContextMenu/ContextMenuSection.tsx (100%) rename app/components/{ContextMenu => modals}/ContextMenuItem/ContextMenuItem.container.tsx (91%) rename app/components/{ContextMenu => modals}/ContextMenuItem/ContextMenuItem.types.ts (71%) rename app/components/{ContextMenu => modals}/ContextMenuItem/ContextMenuItem.view.tsx (100%) rename app/components/{ContextMenu => modals}/ContextMenuItem/index.tsx (100%) delete mode 100644 app/components/navigation/ConditionalFooter.tsx rename app/components/sections/GovernanceTemplateGrid/{index.ts => index.tsx} (100%) rename app/components/{ => sections}/WebVitalsDashboard/WebVitalsDashboard.container.tsx (98%) rename app/components/{ => sections}/WebVitalsDashboard/WebVitalsDashboard.types.ts (100%) rename app/components/{ => sections}/WebVitalsDashboard/WebVitalsDashboard.view.tsx (100%) rename app/components/{ => sections}/WebVitalsDashboard/index.tsx (100%) rename app/components/utility/{ => AvatarContainer}/AvatarContainer.tsx (62%) create mode 100644 app/components/utility/AvatarContainer/index.tsx delete mode 100644 app/components/utility/ErrorBoundary.tsx delete mode 100644 app/components/utility/ImagePlaceholder.tsx create mode 100644 app/components/utility/ModalFooter/ModalFooter.container.tsx create mode 100644 app/components/utility/ModalHeader/ModalHeader.container.tsx create mode 100644 app/components/utility/Scrollbar/Scrollbar.container.tsx rename app/components/utility/{ => Separator}/Separator.tsx (100%) create mode 100644 app/components/utility/Separator/index.tsx delete mode 100644 app/hooks/useComponentStyles.ts delete mode 100644 app/hooks/useFormValidation.ts delete mode 100644 docs/CUSTOM_HOOKS.md delete mode 100644 docs/TESTING_GUIDE.md rename docs/{ => guides}/backend-linear-tickets.md (86%) rename docs/{ => guides}/backend-roadmap.md (91%) delete mode 100644 docs/guides/container-presentation-pattern.md rename docs/{ => guides}/template-recommendation-matrix.md (96%) create mode 100644 docs/testing-guide.md delete mode 100644 lib/cache.ts delete mode 100644 lib/i18n/types.ts delete mode 100644 lib/mdx.ts delete mode 100644 lib/types.ts delete mode 100755 scripts/test-local.sh create mode 100644 stories/asset/Icon.stories.js create mode 100644 stories/cards/TemplateReviewCard.stories.js create mode 100644 stories/controls/Chip.stories.js create mode 100644 stories/controls/InputWithCounter.stories.js create mode 100644 stories/controls/MultiSelect.stories.js create mode 100644 stories/localization/LanguageSwitcher.stories.js create mode 100644 stories/navigation/NavigationItem.stories.js create mode 100644 stories/sections/CommunityRuleDocument.stories.js create mode 100644 stories/sections/GovernanceTemplateGrid.stories.js delete mode 100644 stories/utility/ErrorBoundary.stories.js create mode 100644 stories/utility/ModalFooter.stories.js create mode 100644 stories/utility/ModalHeader.stories.js create mode 100644 stories/utility/Separator.stories.js create mode 100644 tests/components/Chip.test.tsx create mode 100644 tests/components/CommunityRuleDocument.test.tsx create mode 100644 tests/components/GovernanceTemplateGrid.test.tsx create mode 100644 tests/components/Icon.test.tsx create mode 100644 tests/components/InputWithCounter.test.tsx create mode 100644 tests/components/LanguageSwitcher.test.tsx create mode 100644 tests/components/ModalFooter.test.tsx create mode 100644 tests/components/ModalHeader.test.tsx create mode 100644 tests/components/NavigationItem.test.tsx create mode 100644 tests/components/Separator.test.tsx create mode 100644 tests/components/TemplateReviewCard.test.tsx rename tests/{components => contexts}/AuthModalContext.test.tsx (94%) diff --git a/.cursor/rules/component-props.mdc b/.cursor/rules/component-props.mdc index d906ffc..200beec 100644 --- a/.cursor/rules/component-props.mdc +++ b/.cursor/rules/component-props.mdc @@ -1,59 +1,51 @@ --- -description: Figma ↔ codebase prop alignment & normalization for design-system components +description: Component prop conventions — lowercase-canonical enums, Figma traceability globs: app/components/**/*.{ts,tsx} alwaysApply: false --- # Component prop alignment -Figma emits PascalCase enum values (`"Standard"`, `"Inverse"`); the codebase -stores lowercase (`"standard"`, `"inverse"`). Components must accept **both**, -normalize to lowercase internally, and never break existing consumer call -sites. +Figma is the source of truth for component **design** (existence, variants, +visual specification). The codebase implements those components using +idiomatic TypeScript naming. Enum props are **lowercase** in code; PascalCase +is a Figma-side concern only. -## Scope +## Enum prop convention -Applies to enum-like string props: `variant`, `size`, `state`, `mode`, `type`, -`position`, `alignment`, `status`, `color`, `palette`. **Skip** for free-form -strings (URLs, classNames), booleans, numbers, or internal-only props. - -## Pattern +- Types use lowercase string unions: `"small" | "medium" | "large"`. +- Do NOT add PascalCase variants to type unions. +- Do NOT call normalizers in containers. The container layer is for `memo`, + derived state, prop defaults, and bound logic — not for casing translation. +- Each enum prop has a sibling `__OPTIONS as const` array + exported alongside the type. Storybook `argTypes` and any runtime guard + consume that array as the single source of valid values. ```typescript -// Type accepts both formats -export type ComponentSizeValue = "small" | "medium" | "Small" | "Medium"; - -// Container normalizes via helpers from lib/propNormalization.ts -import { normalizeSize } from "../../../lib/propNormalization"; - -const ComponentContainer = ({ size: sizeProp = "small" }: Props) => { - const size = normalizeSize(sizeProp, SIZE_OPTIONS, "small"); - return ; -}; +export const CHIP_PALETTE_OPTIONS = ["primary", "secondary"] as const; +export type ChipPaletteValue = (typeof CHIP_PALETTE_OPTIONS)[number]; ``` -## Normalizers - -Use an existing helper from `lib/propNormalization.ts` before writing a new -one. Generic: `normalizeMode`, `normalizeState`, `normalizeInputState`, -`normalizeSize`, `normalizeAlignment`, `normalizeSmallMediumLargeSize`, -`normalizeLabelVariant`. Component-specific variants are named -`normalize` (e.g. `normalizeChipPalette`, -`normalizeButtonState`) — add one there rather than inlining a switch in your -container. - ## Figma traceability -Every DS component's container docstring cites its Figma origin so designers -and engineers can jump between the two. Format: `Figma: "" -()`. +- Container docstring (required on every DS container): `Figma: + "" ()`. +- View root element: `data-figma-node=""` when the view maps to a + distinct Figma node. +- For create-flow screens, node ids come from `CREATE_FLOW_SCREEN_REGISTRY` + in `app/(app)/create/utils/createFlowScreenRegistry.ts`. For everything else, + pull the node id from the Figma file directly. Use `TODO(figma)` as a + placeholder rather than omitting the docstring entirely. ```typescript /** - * Figma: "Control / Incrementer" (`17857:30943`). A compact [ - value + ] - * row used for numeric step inputs… + * Figma: "Control / Incrementer" (17857:30943). A compact [ - value + ] + * row used for numeric step inputs. */ ``` -When the view renders a distinct Figma node, add `data-figma-node=""` on -the root element for quick DOM-to-design lookup. +## Pasting from Figma + +Figma's "Inspect → Code" output emits PascalCase. When importing a snippet, +lowercase the enum values before committing — same pattern as removing +inline pixel values in favor of design tokens. diff --git a/.cursor/rules/component-structure.mdc b/.cursor/rules/component-structure.mdc index 6bd9f32..449befe 100644 --- a/.cursor/rules/component-structure.mdc +++ b/.cursor/rules/component-structure.mdc @@ -22,24 +22,27 @@ app/components/controls// **Container** (`.container.tsx`): - Marked `"use client"`. -- Receives `Props`; normalizes PascalCase enums via - `lib/propNormalization.ts`; computes derived state (clamps, ids, bounds). -- Renders `<View />` with already-normalized `ViewProps`. +- Receives `Props`; computes derived state (clamps, ids, bounds, prop + defaults) and bound event handlers. +- Renders `<View />`. Containers do **not** translate prop casing — + enum props are lowercase end-to-end (see `component-props.mdc`). - Default export: `memo(Container)` with `.displayName = ""`. +- Carries the Figma docstring (`Figma: "" ()`). **View** (`.view.tsx`): - Marked `"use client"`. -- Pure render of `ViewProps`. No prop normalization, no data fetching, - no derived business logic. -- Default export: `memo(View)` with - `.displayName = "View"`. +- Pure render of `ViewProps`. No data fetching, no derived business + logic, no enum casing translation. +- Default export: `memo(View)` with `.displayName = "View"`. **Types** (`.types.ts`): -- Export `Props` (consumer-facing, accepts PascalCase + lowercase). -- Export `ViewProps` (already-normalized shape the view consumes). -- Export any locally-defined value types (`SizeValue`, etc.). +- Export `Props` (consumer-facing). +- Export `ViewProps` (the shape the view consumes — typically a + resolved superset of `Props`). +- Export any locally-defined value types (`SizeValue`, etc.) sourced + from the matching `*_OPTIONS` array in `lib/propNormalization.ts`. **Index** (`index.tsx`): @@ -51,10 +54,10 @@ export type { Props } from "./.types"; ## Single-file pattern (exception) `app/components/buttons/*.tsx` and other trivially-presentational components -can stay as a single file when they have **no enum prop normalization and no -derived state** (e.g. `Button.tsx`, `InlineTextButton.tsx`). If you find -yourself adding state, enum normalization, or more than a handful of props, -promote it to the split pattern. +can stay as a single file when they have **no derived state and only a +handful of props** (e.g. `Button.tsx`, `InlineTextButton.tsx`). If you find +yourself adding state, side effects, or enum logic, promote it to the split +pattern. ## Wrapper / group components diff --git a/.cursor/rules/create-flow.mdc b/.cursor/rules/create-flow.mdc index e64c450..9c9a417 100644 --- a/.cursor/rules/create-flow.mdc +++ b/.cursor/rules/create-flow.mdc @@ -1,6 +1,6 @@ --- description: Create-flow structure & design-system reuse guardrails -globs: app/create/**/*.{ts,tsx},messages/en/create/**/*.json +globs: app/(app)/create/**/*.{ts,tsx},messages/en/create/**/*.json alwaysApply: false --- @@ -8,15 +8,15 @@ alwaysApply: false ## Folder & file layout -- Screens live in `app/create/screens//Screen.tsx` +- Screens live in `app/(app)/create/screens//Screen.tsx` where `` mirrors `CreateFlowLayoutKind` (`card`, `select`, `right-rail`, `completed`, …). File + export name is the **step id**, never the layout kind (e.g. `DecisionApproachesScreen`, not `RightRailScreen`). - Step id ↔ layout kind mapping is declared in - `app/create/utils/createFlowScreenRegistry.ts`. Never branch on layout kind + `app/(app)/create/utils/createFlowScreenRegistry.ts`. Never branch on layout kind inside a screen — pick the matching shell (`CreateFlowStepShell` / `CreateFlowTwoColumnSelectShell`). -- Shared create-flow pieces go in `app/create/components/` (layout shells, +- Shared create-flow pieces go in `app/(app)/create/components/` (layout shells, field composites). Generic primitives go in `app/components/`. ## Use the design system — don't hand-roll @@ -25,8 +25,8 @@ Reach for these before writing new markup: | Need | Component | | --- | --- | -| Labelled text-area section in a modal | `app/create/components/ModalTextAreaField` | -| Toggle-chip row + inline "+ Add" input | `app/create/components/ApplicableScopeField` | +| Labelled text-area section in a modal | `app/(app)/create/components/ModalTextAreaField` | +| Toggle-chip row + inline "+ Add" input | `app/(app)/create/components/ApplicableScopeField` | | `[– value +]` numeric stepper (± label) | `app/components/controls/Incrementer` / `IncrementerBlock` | | Mid-paragraph "expand / see all" link button | `app/components/buttons/InlineTextButton` | | Help-icon + label above a control | `app/components/utility/InputLabel` (`helpIcon` prop) | diff --git a/.cursor/rules/hooks.mdc b/.cursor/rules/hooks.mdc new file mode 100644 index 0000000..245199e --- /dev/null +++ b/.cursor/rules/hooks.mdc @@ -0,0 +1,59 @@ +--- +description: Custom hooks live in app/hooks; co-locate logic, document via TSDoc. +globs: app/hooks/**/*.{ts,tsx} +alwaysApply: false +--- + +# Custom hooks + +Reusable component logic lives in `app/hooks/`. Each hook is a small, focused +module with a TSDoc block that doubles as the API reference (no separate doc +file). + +## File layout + +- One file per hook: `app/hooks/use.ts`. +- Re-export from `app/hooks/index.ts`. Consumers import from the barrel: + `import { useFoo } from "../hooks";`. +- Companion unit test (when there is non-trivial logic): `tests/unit/hooks/`. + +## Authoring rules + +- Marked as a regular function (`export function useFoo() {}`); React handles + the `use*` naming convention. +- Wrap exposed callbacks in `useCallback` and computed values in `useMemo` + so consumers can list them in dependency arrays without churn. +- Read DOM/browser APIs only inside `useEffect` so the hook stays SSR-safe. +- Never throw on missing globals (e.g. `window`, `gtag`); guard and no-op. + +## TSDoc — the only reference + +Every exported hook gets a TSDoc block with: + +- 1–2 sentence summary. +- `@param` per argument and `@returns` describing the shape. +- `@example` showing the typical call site. + +```ts +/** + * Detect clicks outside a set of elements (e.g. close a dropdown). + * + * @param refs Elements that should NOT trigger the handler. + * @param handler Invoked when a click lands outside every ref. + * @param enabled Toggle without unmounting the consumer (default true). + * + * @example + * useClickOutside([menuRef, buttonRef], () => setOpen(false), open); + */ +export function useClickOutside( + refs: Array>, + handler: (event: MouseEvent | TouchEvent) => void, + enabled = true, +): void { /* ... */ } +``` + +## Container/view consumption + +Hooks belong in **container** files (per `component-structure.mdc`). Views +stay pure and read derived values via props — never call hooks that touch +state or side effects from a view. diff --git a/.cursor/rules/routes.mdc b/.cursor/rules/routes.mdc new file mode 100644 index 0000000..1f9d9dd --- /dev/null +++ b/.cursor/rules/routes.mdc @@ -0,0 +1,88 @@ +--- +description: App Router route organization (groups, layouts, chrome composition) +globs: app/**/*.{ts,tsx} +alwaysApply: false +--- + +# Route organization + +Top-level routes live inside **route groups** so each surface owns its own +layout and chrome. Groups are wrapping folders in `(parens)` — they organize +the file tree without affecting URLs. + +## Group map + +| Group | URL surface | Audience | Chrome | +|---|---|---|---| +| `app/(marketing)/` | `/`, `/learn`, `/blog`, `/templates`, future public pages | Public, indexable | TopNav (via root) + marketing `