From 11ba3814e5bae9629eb71fe89fafdd0ec32ef45c Mon Sep 17 00:00:00 2001 From: James Brunton Date: Fri, 3 Jul 2026 14:20:02 +0100 Subject: [PATCH] Restructure Portal code to be inside Editor (#6857) # Description of Changes We don't have any strong reasons to keep the Portal as a separate Vite app, and it needs access to so many things from the Editor that it no longer makes sense to keep them separate. This PR moves the Portal code to have direct access to the Editor code and gets rid of the shared folder. --- .dockerignore | 1 - .gitleaksignore | 4 +- .taskfiles/frontend.yml | 82 +- AGENTS.md | 3 +- LICENSE | 4 +- Taskfile.yml | 109 +- .../access/model/ResourceType.java | 3 +- frontend/.gitignore | 3 +- frontend/.prettierignore | 3 +- frontend/.storybook/main.ts | 29 +- frontend/.storybook/preview.tsx | 6 +- frontend/editor/.env.proprietary | 32 + .../public/locales/en-US/translation.toml | 1851 ++++++++++++++++ .../public/mockServiceWorker.js | 0 .../config/configSections/SpendCapControl.tsx | 4 +- .../config/configSections/usageMeters.tsx | 2 +- frontend/editor/src/cloud/hooks/useWallet.ts | 4 +- frontend/editor/src/cloud/tsconfig.json | 4 +- .../src/core}/assets/Brand.stories.tsx | 20 +- .../assets/brand/classic-logo/Firstpage.png | Bin .../classic-logo/StirlingPDFLogoBlackText.svg | 0 .../classic-logo/StirlingPDFLogoGreyText.svg | 0 .../StirlingPDFLogoNoTextDark.svg | 0 .../StirlingPDFLogoNoTextLight.svg | 0 .../classic-logo/StirlingPDFLogoWhiteText.svg | 0 .../assets/brand/classic-logo/favicon.ico | Bin .../brand/classic-logo/logo-tooltip.svg | 0 .../assets/brand/classic-logo/logo192.png | Bin .../assets/brand/classic-logo/logo512.png | Bin .../assets/brand/modern-logo/Firstpage.png | Bin .../brand/modern-logo/LoginDarkModeHeader.svg | 0 .../modern-logo/LoginLightModeHeader.svg | 0 .../modern-logo/StirlingPDFLogoBlackText.svg | 0 .../modern-logo/StirlingPDFLogoGreyText.svg | 0 .../modern-logo/StirlingPDFLogoNoTextDark.svg | 0 .../StirlingPDFLogoNoTextLight.svg | 0 .../modern-logo/StirlingPDFLogoWhiteText.svg | 0 .../assets/brand/modern-logo}/favicon.ico | Bin .../assets/brand/modern-logo/logo-tooltip.svg | 0 .../assets/brand/modern-logo/logo192.png | Bin .../assets/brand/modern-logo/logo512.png | Bin .../src/core}/assets/login/AddToPDF.png | Bin .../src/core}/assets/login/Firstpage.png | Bin .../assets/login/LoginBackgroundPanel.png | Bin .../src/core}/assets/login/SecurePDF.png | Bin .../src/core}/assets/login/apple.svg | 0 .../src/core}/assets/login/authentik.svg | 0 .../src/core}/assets/login/cloudron.svg | 0 .../src/core}/assets/login/github.svg | 0 .../src/core}/assets/login/google.svg | 0 .../src/core}/assets/login/keycloak.svg | 0 .../src/core}/assets/login/microsoft.svg | 0 .../src/core}/assets/login/oidc.svg | 0 .../src/core}/auth/ui/oauthIcons.ts | 16 +- .../core/components/shared/ThemeProvider.tsx | 4 +- .../components/shared/ToolPanelHeader.css | 93 + .../components/shared/ToolPanelHeader.tsx | 53 + .../configSections/providerDefinitions.ts | 2 +- .../core/components/tools/RightSidebar.tsx | 4 +- frontend/editor/src/core/env.test.ts | 5 + .../src/core/hooks/useLogoAssets.test.ts | 10 +- frontend/editor/src/core/i18n.ts | 6 +- frontend/editor/src/core/i18n/config.ts | 2 +- .../src/core}/i18n/languages.ts | 0 .../src/core}/i18n/tomlBackend.ts | 0 .../src/core}/i18n/translationAudit.ts | 47 +- .../src/core/routes/adminRouteExtensions.tsx | 10 + .../core/tests/missingTranslations.test.ts | 4 +- .../src/core/tests/unusedTranslations.test.ts | 6 +- .../src/core}/tokens/Tokens.stories.tsx | 0 .../src/core}/tokens/base.css | 0 .../src/core}/tokens/tokens.css | 0 .../src/core}/tokens/tokens.ts | 0 frontend/editor/src/core/tsconfig.json | 3 +- .../SetupWizard/DesktopAuthLayout.tsx | 4 +- .../SetupWizard/DesktopOAuthButtons.tsx | 2 +- .../SetupWizard/SaaSLoginScreen.tsx | 6 +- .../SetupWizard/SaaSSignupScreen.tsx | 4 +- .../components/SetupWizard/SelfHostedLink.tsx | 2 +- .../SetupWizard/SelfHostedLoginScreen.tsx | 6 +- .../SetupWizard/ServerSelectionScreen.tsx | 4 +- .../desktop/components/SetupWizard/index.tsx | 2 +- .../desktop/routes/adminRouteExtensions.tsx | 10 + frontend/editor/src/desktop/tsconfig.json | 3 +- frontend/{ => editor/src}/portal/LICENSE | 0 frontend/{ => editor/src}/portal/MOCKS.md | 0 .../src/portal/PortalApp.tsx} | 42 +- .../{ => editor/src}/portal/UI_CONVENTIONS.md | 4 +- frontend/editor/src/portal/ViewRouter.tsx | 58 + .../src => editor/src/portal}/api/agents.ts | 0 .../src/portal}/api/assistant.ts | 0 .../src => editor/src/portal}/api/billing.ts | 6 +- .../src => editor/src/portal}/api/docs.ts | 0 .../src/portal}/api/documents.ts | 0 .../src/portal}/api/editorDeploy.ts | 0 .../src => editor/src/portal}/api/home.ts | 0 .../src/portal}/api/http.test.ts | 4 +- .../src => editor/src/portal}/api/http.ts | 6 +- .../src/portal}/api/infrastructure.ts | 0 .../src/portal}/api/link.test.ts | 2 +- .../src => editor/src/portal}/api/link.ts | 0 .../src/portal}/api/notifications.ts | 0 .../src => editor/src/portal}/api/ops.ts | 0 .../src/portal}/api/pipelines.ts | 0 .../src => editor/src/portal}/api/policies.ts | 10 +- .../src/portal}/api/procurement.ts | 0 .../src/portal}/api/sdkComponents.ts | 0 .../src => editor/src/portal}/api/search.ts | 0 .../src => editor/src/portal}/api/settings.ts | 0 .../src => editor/src/portal}/api/sources.ts | 0 .../src => editor/src/portal}/api/users.ts | 0 .../src/portal}/auth/editorUrl.ts | 2 +- .../src/portal}/auth/saasSupabase.ts | 2 +- .../portal}/auth/saasSupabaseLogin.test.ts | 4 +- .../billing/sharedBillingFormat.test.ts | 4 +- .../src/portal}/billing/stripe.test.ts | 2 +- .../src/portal}/billing/stripe.ts | 2 +- .../src/portal}/components/AppShell.css | 0 .../src/portal}/components/AppShell.tsx | 0 .../portal}/components/AssistantButton.css | 0 .../portal}/components/AssistantButton.tsx | 6 +- .../src/portal}/components/AssistantPanel.css | 0 .../components/AssistantPanel.stories.tsx | 0 .../src/portal}/components/AssistantPanel.tsx | 20 +- .../src/portal}/components/AuthGate.tsx | 8 +- .../components/ChatFABWidget.stories.tsx | 4 +- .../portal}/components/ErrorBoundary.test.tsx | 14 +- .../src/portal}/components/ErrorBoundary.tsx | 10 +- .../src/portal}/components/Header.css | 0 .../src/portal}/components/Header.stories.tsx | 0 .../src/portal}/components/Header.tsx | 25 +- .../src/portal}/components/LoginScreen.tsx | 18 +- .../src/portal}/components/MocksToggle.css | 0 .../src/portal}/components/MocksToggle.tsx | 6 +- .../components/NotificationsDropdown.css | 0 .../NotificationsDropdown.stories.tsx | 0 .../components/NotificationsDropdown.tsx | 22 +- .../portal}/components/PipelineForkWizard.css | 0 .../components/PipelineForkWizard.stories.tsx | 0 .../portal}/components/PipelineForkWizard.tsx | 18 +- .../src/portal}/components/PolicySummary.css | 0 .../components/PolicySummary.stories.tsx | 0 .../src/portal}/components/PolicySummary.tsx | 37 +- .../portal}/components/PopularUseCases.css | 0 .../portal}/components/PopularUseCases.tsx | 20 +- .../components/ProcessingStatusStrip.css | 0 .../ProcessingStatusStrip.stories.tsx | 0 .../components/ProcessingStatusStrip.tsx | 13 +- .../src/portal}/components/RecentActivity.css | 0 .../components/RecentActivity.stories.tsx | 0 .../src/portal}/components/RecentActivity.tsx | 14 +- .../src/portal}/components/SearchModal.css | 0 .../components/SearchModal.stories.tsx | 0 .../src/portal}/components/SearchModal.tsx | 16 +- .../src/portal}/components/SettingsModal.css | 0 .../src/portal}/components/SettingsModal.tsx | 138 +- .../src/portal}/components/Sidebar.css | 0 .../portal}/components/Sidebar.stories.tsx | 0 .../src/portal}/components/Sidebar.tsx | 30 +- .../src/portal}/components/SingleOpRunner.css | 0 .../components/SingleOpRunner.stories.tsx | 0 .../src/portal}/components/SingleOpRunner.tsx | 70 +- .../src/portal}/components/UsageAreaChart.css | 0 .../components/UsageAreaChart.stories.tsx | 0 .../src/portal}/components/UsageAreaChart.tsx | 8 +- .../portal}/components/WelcomeCarousel.css | 0 .../components/WelcomeCarousel.stories.tsx | 0 .../portal}/components/WelcomeCarousel.tsx | 48 +- .../account-link/AccountLinkPanel.tsx | 16 +- .../account-link/LinkAccountCard.stories.tsx | 0 .../account-link/LinkAccountCard.tsx | 31 +- .../account-link/LinkAccountModal.tsx | 32 +- .../account-link/LinkGate.stories.tsx | 2 +- .../components/account-link/LinkGate.tsx | 18 +- .../LinkedInstancesTable.stories.tsx | 0 .../account-link/LinkedInstancesTable.tsx | 37 +- .../AgentBuilderPanel.stories.tsx | 0 .../agent-builder/AgentBuilderPanel.tsx | 12 +- .../agent-builder/AgentKpiStrip.stories.tsx | 0 .../agent-builder/AgentKpiStrip.tsx | 20 +- .../agent-builder/AgentSelector.stories.tsx | 0 .../agent-builder/AgentSelector.tsx | 4 +- .../agent-builder/BootstrapDialog.stories.tsx | 2 +- .../agent-builder/BootstrapDialog.tsx | 14 +- .../agent-builder/EvalsPanel.stories.tsx | 0 .../components/agent-builder/EvalsPanel.tsx | 30 +- .../agent-builder/ScenariosPanel.stories.tsx | 0 .../agent-builder/ScenariosPanel.tsx | 32 +- .../agent-builder/ToolsPanel.stories.tsx | 0 .../components/agent-builder/ToolsPanel.tsx | 16 +- .../agent-builder/VersionsPanel.stories.tsx | 0 .../agent-builder/VersionsPanel.tsx | 10 +- .../billing/EnterpriseUpsell.stories.tsx | 0 .../components/billing/EnterpriseUpsell.tsx | 16 +- .../billing/FreePdfEditorsCard.stories.tsx | 0 .../components/billing/FreePdfEditorsCard.tsx | 30 +- .../billing/FreePlanView.stories.tsx | 0 .../components/billing/FreePlanView.tsx | 23 +- .../components/billing/InvoicesList.tsx | 51 +- .../billing/LinkAccountPrompt.stories.tsx | 0 .../components/billing/LinkAccountPrompt.tsx | 11 +- .../billing/PaymentMethodCard.stories.tsx | 0 .../components/billing/PaymentMethodCard.tsx | 24 +- .../billing/PdfsProcessedCard.stories.tsx | 0 .../components/billing/PdfsProcessedCard.tsx | 27 +- .../billing/SpendLimitCard.stories.tsx | 0 .../components/billing/SpendLimitCard.tsx | 60 +- .../billing/SpendThisMonthCard.stories.tsx | 0 .../components/billing/SpendThisMonthCard.tsx | 10 +- .../billing/StripeCheckoutModal.tsx | 19 +- .../billing/SubscribedPlanView.stories.tsx | 0 .../components/billing/SubscribedPlanView.tsx | 19 +- .../billing/WalletMeter.stories.tsx | 0 .../components/billing/WalletMeter.tsx | 26 +- .../portal}/components/billing/billing.css | 0 .../components/billing/walletFixtures.ts | 0 .../catalogue/ComponentCard.stories.tsx | 0 .../components/catalogue/ComponentCard.tsx | 8 +- .../ComponentDetailModal.stories.tsx | 0 .../catalogue/ComponentDetailModal.tsx | 48 +- .../catalogue/ComponentGrid.stories.tsx | 0 .../components/catalogue/ComponentGrid.tsx | 0 .../catalogue/ComponentPropsTable.stories.tsx | 0 .../catalogue/ComponentPropsTable.tsx | 14 +- .../catalogue/ComponentsSummaryStrip.tsx | 10 +- .../docs/AuthenticationSection.stories.tsx | 0 .../components/docs/AuthenticationSection.tsx | 14 +- .../docs/ComponentsSection.stories.tsx | 0 .../components/docs/ComponentsSection.tsx | 10 +- .../components/docs/DocsNav.stories.tsx | 0 .../src/portal}/components/docs/DocsNav.tsx | 7 +- .../components/docs/DocsSection.stories.tsx | 2 +- .../portal}/components/docs/DocsSection.tsx | 0 .../docs/EndpointReferenceSection.stories.tsx | 0 .../docs/EndpointReferenceSection.tsx | 21 +- .../components/docs/ErrorsSection.stories.tsx | 0 .../portal}/components/docs/ErrorsSection.tsx | 10 +- .../docs/GettingStartedSection.stories.tsx | 0 .../components/docs/GettingStartedSection.tsx | 32 +- .../components/docs/LangSnippet.stories.tsx | 0 .../portal}/components/docs/LangSnippet.tsx | 2 +- .../docs/PlaybooksSection.stories.tsx | 0 .../components/docs/PlaybooksSection.tsx | 10 +- .../docs/RateLimitsSection.stories.tsx | 0 .../components/docs/RateLimitsSection.tsx | 16 +- .../components/docs/SdksSection.stories.tsx | 0 .../portal}/components/docs/SdksSection.tsx | 15 +- .../components/docs/SkillsSection.stories.tsx | 0 .../portal}/components/docs/SkillsSection.tsx | 8 +- .../docs/WebhooksSection.stories.tsx | 0 .../components/docs/WebhooksSection.tsx | 17 +- .../documents/DocumentAudit.stories.tsx | 0 .../components/documents/DocumentAudit.tsx | 6 +- .../documents/DocumentDrawer.stories.tsx | 0 .../components/documents/DocumentDrawer.tsx | 13 +- .../documents/DocumentExtractions.stories.tsx | 0 .../documents/DocumentExtractions.tsx | 12 +- .../documents/DocumentOverview.stories.tsx | 0 .../components/documents/DocumentOverview.tsx | 23 +- .../DocumentsSummaryStrip.stories.tsx | 0 .../documents/DocumentsSummaryStrip.tsx | 10 +- .../documents/ElevationBanner.stories.tsx | 0 .../components/documents/ElevationBanner.tsx | 16 +- .../documents/ReviewQueue.stories.tsx | 0 .../components/documents/ReviewQueue.tsx | 16 +- .../documents/ReviewQueueTable.stories.tsx | 0 .../components/documents/ReviewQueueTable.tsx | 27 +- .../portal}/components/documents/format.ts | 2 +- .../CredentialRotationCard.stories.tsx | 0 .../editor-admin/CredentialRotationCard.tsx | 18 +- .../DeploymentSummaryStrip.stories.tsx | 0 .../editor-admin/DeploymentSummaryStrip.tsx | 2 +- .../DeploymentTargets.stories.tsx | 0 .../editor-admin/DeploymentTargets.tsx | 14 +- .../InstanceHealthTable.stories.tsx | 0 .../editor-admin/InstanceHealthTable.tsx | 18 +- .../OfflineActivationCard.stories.tsx | 0 .../editor-admin/OfflineActivationCard.tsx | 20 +- .../editor-admin/PairingPanel.stories.tsx | 0 .../components/editor-admin/PairingPanel.tsx | 12 +- .../src/portal}/components/icons.tsx | 0 .../infrastructure/ApiKeyCard.stories.tsx | 0 .../components/infrastructure/ApiKeyCard.tsx | 20 +- .../infrastructure/ApiKeysTab.stories.tsx | 0 .../components/infrastructure/ApiKeysTab.tsx | 12 +- .../infrastructure/AuditTab.stories.tsx | 0 .../components/infrastructure/AuditTab.tsx | 59 +- .../infrastructure/CreateKeyModal.stories.tsx | 0 .../infrastructure/CreateKeyModal.tsx | 34 +- .../infrastructure/DeploymentsTab.stories.tsx | 0 .../infrastructure/DeploymentsTab.tsx | 52 +- .../infrastructure/ModelsTab.stories.tsx | 0 .../components/infrastructure/ModelsTab.tsx | 75 +- .../infrastructure/SectionHeader.stories.tsx | 0 .../infrastructure/SectionHeader.tsx | 0 .../infrastructure/SecurityTab.stories.tsx | 0 .../components/infrastructure/SecurityTab.tsx | 106 +- .../infrastructure/StorageTab.stories.tsx | 0 .../components/infrastructure/StorageTab.tsx | 70 +- .../infrastructure/TableSkeleton.stories.tsx | 2 +- .../infrastructure/TableSkeleton.tsx | 2 +- .../components/infrastructure/infraFormat.ts | 2 +- .../portal}/components/pipelines/KpiStrip.tsx | 8 +- .../components/pipelines/PipelineComposer.tsx | 56 +- .../pipelines/PipelineDetailCard.tsx | 40 +- .../components/pipelines/PipelinesTable.tsx | 14 +- .../pipelines/pipelineOperations.ts | 0 .../policies/CatalogueSummary.stories.tsx | 0 .../components/policies/CatalogueSummary.tsx | 18 +- .../policies/PolicyCategoryCard.stories.tsx | 0 .../policies/PolicyCategoryCard.tsx | 14 +- .../policies/PolicyDetailPanel.stories.tsx | 0 .../components/policies/PolicyDetailPanel.tsx | 48 +- .../policies/PolicyFieldRow.stories.tsx | 0 .../components/policies/PolicyFieldRow.tsx | 8 +- .../policies/PolicySetupWizard.stories.tsx | 0 .../components/policies/PolicySetupWizard.tsx | 102 +- .../components/policies/policyIcons.ts | 0 .../components/policies/storyFixtures.ts | 4 +- .../procurement/ActionModal.stories.tsx | 0 .../components/procurement/ActionModal.tsx | 40 +- .../procurement/DealJourney.stories.tsx | 0 .../components/procurement/DealJourney.tsx | 20 +- .../components/procurement/DocRow.stories.tsx | 0 .../portal}/components/procurement/DocRow.tsx | 8 +- .../procurement/DocumentLedger.stories.tsx | 0 .../components/procurement/DocumentLedger.tsx | 20 +- .../procurement/LockedState.stories.tsx | 0 .../components/procurement/LockedState.tsx | 10 +- .../procurement/StageStepper.stories.tsx | 0 .../components/procurement/StageStepper.tsx | 0 .../portal}/components/procurement/format.ts | 22 +- .../sources/ConnectWizard.stories.tsx | 0 .../components/sources/ConnectWizard.test.tsx | 12 +- .../components/sources/ConnectWizard.tsx | 37 +- .../components/sources/KpiStrip.stories.tsx | 0 .../portal}/components/sources/KpiStrip.tsx | 8 +- .../sources/SourceDetailCard.stories.tsx | 0 .../components/sources/SourceDetailCard.tsx | 16 +- .../sources/SourceDetailPanel.stories.tsx | 0 .../components/sources/SourceDetailPanel.tsx | 16 +- .../sources/SourcesTable.stories.tsx | 0 .../components/sources/SourcesTable.tsx | 10 +- .../components/sources/Sparkline.test.tsx | 0 .../portal}/components/sources/Sparkline.tsx | 0 .../portal}/components/sources/sourceTypes.ts | 35 +- .../users/AccessControls.stories.tsx | 0 .../components/users/AccessControls.tsx | 52 +- .../users/InviteMemberModal.stories.tsx | 0 .../components/users/InviteMemberModal.tsx | 24 +- .../components/users/MembersTable.stories.tsx | 0 .../portal}/components/users/MembersTable.tsx | 26 +- .../components/users/RolesGrid.stories.tsx | 0 .../portal}/components/users/RolesGrid.tsx | 8 +- .../users/UsersSummaryStrip.stories.tsx | 0 .../components/users/UsersSummaryStrip.tsx | 4 +- .../src/portal}/components/users/format.ts | 2 +- .../portal}/contexts/AccountLinkContext.tsx | 0 .../src/portal}/contexts/LinkContext.test.tsx | 0 .../src/portal}/contexts/LinkContext.tsx | 6 +- .../src/portal}/contexts/ThemeContext.tsx | 0 .../src/portal}/contexts/TierContext.tsx | 0 .../src/portal}/contexts/UIContext.tsx | 0 .../src/portal}/contexts/ViewContext.tsx | 24 +- .../src/portal}/data/Endpoints.stories.tsx | 6 +- .../src/portal}/data/Ops.stories.tsx | 2 +- .../src/portal}/data/endpoints.ts | 2 +- .../{shared => editor/src/portal}/data/ops.ts | 0 .../src/portal}/hooks/useAccountLink.test.tsx | 0 .../src/portal}/hooks/useAccountLink.ts | 2 +- .../src/portal}/hooks/useAsync.ts | 0 .../src/portal}/hooks/useStripePortal.ts | 0 .../src => editor/src/portal}/mocks/agents.ts | 0 .../src/portal}/mocks/assistant.ts | 0 .../src => editor/src/portal}/mocks/auth.ts | 0 frontend/editor/src/portal/mocks/browser.ts | 26 + .../src => editor/src/portal}/mocks/docs.ts | 2 +- .../src/portal}/mocks/documents.ts | 2 +- .../src/portal}/mocks/editorDeploy.ts | 0 .../src/portal}/mocks/handlers/agents.ts | 0 .../src/portal}/mocks/handlers/assistant.ts | 0 .../src/portal}/mocks/handlers/auth.ts | 0 .../src/portal}/mocks/handlers/docs.ts | 0 .../src/portal}/mocks/handlers/documents.ts | 0 .../portal}/mocks/handlers/editorDeploy.ts | 0 .../src/portal}/mocks/handlers/home.ts | 0 .../src/portal}/mocks/handlers/index.ts | 29 + .../portal}/mocks/handlers/infrastructure.ts | 0 .../src/portal}/mocks/handlers/link.ts | 0 .../portal}/mocks/handlers/notifications.ts | 0 .../src/portal}/mocks/handlers/ops.ts | 0 .../src/portal}/mocks/handlers/pipelines.ts | 0 .../src/portal}/mocks/handlers/policies.ts | 2 +- .../src/portal}/mocks/handlers/procurement.ts | 0 .../portal}/mocks/handlers/sdkComponents.ts | 0 .../src/portal}/mocks/handlers/search.ts | 0 .../src/portal}/mocks/handlers/settings.ts | 0 .../src/portal}/mocks/handlers/sources.ts | 0 .../src/portal}/mocks/handlers/users.ts | 0 .../src => editor/src/portal}/mocks/home.ts | 0 .../src/portal}/mocks/infrastructure.ts | 0 .../src/portal}/mocks/link.test.ts | 0 .../src => editor/src/portal}/mocks/link.ts | 0 .../src/portal}/mocks/notifications.ts | 0 .../src => editor/src/portal}/mocks/ops.ts | 0 .../src/portal}/mocks/policies.ts | 10 +- .../src/portal}/mocks/preference.ts | 0 .../src/portal}/mocks/procurement.ts | 0 .../portal}/mocks/procurementMachine.test.ts | 0 .../src/portal}/mocks/procurementMachine.ts | 0 .../src/portal}/mocks/sampleDailySeries.ts | 0 .../src/portal}/mocks/sdkComponents.ts | 0 .../src => editor/src/portal}/mocks/search.ts | 0 .../src/portal}/mocks/settings.ts | 0 .../editor/src/portal/mocks/startIfEnabled.ts | 12 + .../src => editor/src/portal}/mocks/users.ts | 0 .../src => editor/src/portal}/setupTests.ts | 0 .../theme/MantineIntegration.stories.tsx | 2 +- frontend/editor/src/portal/theme/base.css | 40 + .../src/portal}/theme/mantineTheme.ts | 0 frontend/editor/src/portal/tsconfig.json | 18 + .../src/portal}/views/AccountLink.css | 0 .../src/portal}/views/AgentBuilder.css | 0 .../src/portal}/views/AgentBuilder.tsx | 18 +- .../src/portal}/views/Components.css | 0 .../src/portal}/views/Components.tsx | 14 +- .../src/portal}/views/DeveloperDocs.css | 0 .../src/portal}/views/DeveloperDocs.tsx | 6 +- .../src/portal}/views/Documents.css | 0 .../src/portal}/views/Documents.tsx | 8 +- .../src/portal}/views/EditorAdmin.css | 0 .../src/portal}/views/EditorAdmin.tsx | 22 +- .../src => editor/src/portal}/views/Home.css | 0 .../src/portal}/views/Home.stories.tsx | 0 .../src => editor/src/portal}/views/Home.tsx | 102 +- .../src/portal}/views/Infrastructure.css | 0 .../src/portal}/views/Infrastructure.tsx | 26 +- .../src/portal}/views/Pipelines.css | 0 .../src/portal}/views/Pipelines.test.tsx | 20 +- .../src/portal}/views/Pipelines.tsx | 38 +- .../src/portal}/views/Policies.css | 0 .../src/portal}/views/Policies.tsx | 12 +- .../src/portal}/views/Procurement.css | 0 .../src/portal}/views/Procurement.tsx | 12 +- .../src/portal}/views/Sources.css | 0 .../src/portal}/views/Sources.test.tsx | 6 +- .../src/portal}/views/Sources.tsx | 32 +- .../src => editor/src/portal}/views/Usage.css | 0 .../src => editor/src/portal}/views/Usage.tsx | 27 +- .../src => editor/src/portal}/views/Users.css | 0 .../src => editor/src/portal}/views/Users.tsx | 14 +- .../src => editor/src/portal}/vite-env.d.ts | 2 +- frontend/editor/src/proprietary/App.tsx | 7 +- .../src/proprietary}/auth/AuthProvider.tsx | 6 +- .../src/proprietary/auth/UseSession.test.ts | 2 +- .../src/proprietary/auth/UseSession.tsx | 8 +- .../src/proprietary}/auth/config.ts | 4 +- .../proprietary/auth/configureSpringAuth.ts | 2 +- .../src/proprietary}/auth/context.ts | 2 +- .../proprietary}/auth/guards/RequireAdmin.tsx | 2 +- .../proprietary}/auth/guards/RequireAuth.tsx | 2 +- .../auth/guards/RequirePortalAccess.tsx | 2 +- .../src/proprietary}/auth/httpClient.ts | 0 .../src/proprietary}/auth/index.ts | 35 +- .../src/proprietary}/auth/roles.ts | 0 .../proprietary}/auth/spring/UseSession.tsx | 10 +- .../proprietary}/auth/spring/oauthStorage.ts | 0 .../proprietary}/auth/spring/oauthTypes.ts | 0 .../auth/spring/platformBridge.ts | 0 .../auth/spring/springAuthClient.ts | 10 +- .../proprietary/auth/springAuthClient.test.ts | 2 +- .../proprietary}/auth/supabase/UseSession.tsx | 8 +- .../auth/supabase/supabaseClient.ts | 0 .../src/proprietary}/auth/types.ts | 0 .../proprietary}/auth/ui/AuthShell.module.css | 0 .../src/proprietary}/auth/ui/AuthShell.tsx | 2 +- .../auth/ui/EmailPasswordForm.tsx | 2 +- .../src/proprietary}/auth/ui/ErrorMessage.tsx | 0 .../auth/ui/LoginRightCarousel.tsx | 2 +- .../src/proprietary}/auth/ui/OAuthButtons.tsx | 7 +- .../proprietary}/auth/ui/SpringLoginForm.tsx | 8 +- .../auth/ui/SupabaseLoginForm.tsx | 10 +- .../src/proprietary}/auth/ui/auth-theme.css | 0 .../src/proprietary}/auth/ui/auth.css | 0 .../proprietary}/auth/ui/loginSlideText.ts | 2 +- .../src/proprietary}/auth/ui/loginSlides.ts | 12 +- .../proprietary}/auth/ui/useSpringLogin.ts | 6 +- .../proprietary}/auth/ui/useSupabaseLogin.ts | 4 +- .../src/proprietary}/billing/MeterBar.tsx | 2 +- .../proprietary}/billing/SpendCapControl.tsx | 4 +- .../src/proprietary}/billing/format.ts | 0 .../src/proprietary}/billing/index.ts | 8 +- .../src/proprietary}/billing/types.ts | 0 .../proprietary/components/chat/ChatFAB.tsx | 4 +- .../proprietary/components/chat/ChatPanel.tsx | 2 +- .../components/policies/Policies.css | 2 +- .../components/policies/PoliciesSidebar.tsx | 6 +- .../components/policies/PolicyDetailPanel.tsx | 16 +- .../components/policies/PolicyFieldRow.tsx | 10 +- .../components/policies/PolicySetupWizard.tsx | 20 +- .../components/policies/PolicyToolConfig.tsx | 4 +- .../proprietary/components/policies/README.md | 8 +- .../components/policies/policyStatus.ts | 2 +- .../components/shared/loginSlides.ts | 8 +- .../src/proprietary}/policies/codec.test.ts | 4 +- .../src/proprietary}/policies/codec.ts | 2 +- .../src/proprietary}/policies/runs.test.ts | 4 +- .../src/proprietary}/policies/runs.ts | 2 +- .../src/proprietary}/policies/types.ts | 0 .../proprietary/routes/AuthCallback.test.tsx | 8 +- .../src/proprietary/routes/AuthCallback.tsx | 2 +- .../src/proprietary/routes/InviteAccept.tsx | 2 +- .../src/proprietary/routes/Login.test.tsx | 10 +- .../editor/src/proprietary/routes/Login.tsx | 10 +- .../editor/src/proprietary/routes/Signup.tsx | 6 +- .../routes/adminRouteExtensions.tsx | 23 + .../routes/authShared/AuthLayout.tsx | 4 +- .../routes/login/OAuthButtons.test.tsx | 2 +- .../proprietary/routes/signup/AuthService.ts | 2 +- .../proprietary/routes/signup/SignupForm.tsx | 2 +- frontend/editor/src/proprietary/tsconfig.json | 4 +- .../src/proprietary/ui}/Avatar.css | 0 .../src/proprietary/ui}/Avatar.stories.tsx | 2 +- .../src/proprietary/ui}/Avatar.tsx | 2 +- .../src/proprietary/ui}/Banner.css | 0 .../src/proprietary/ui}/Banner.stories.tsx | 4 +- .../src/proprietary/ui}/Banner.tsx | 2 +- .../src/proprietary/ui}/Button.css | 0 .../src/proprietary/ui}/Button.stories.tsx | 2 +- .../src/proprietary/ui}/Button.tsx | 2 +- .../src/proprietary/ui}/Card.css | 0 .../src/proprietary/ui}/Card.stories.tsx | 8 +- .../src/proprietary/ui}/Card.tsx | 2 +- .../src/proprietary/ui}/ChatFABButton.css | 0 .../proprietary/ui}/ChatFABButton.stories.tsx | 2 +- .../src/proprietary/ui}/ChatFABButton.tsx | 2 +- .../src/proprietary/ui}/ChatFABWindow.css | 0 .../proprietary/ui}/ChatFABWindow.stories.tsx | 2 +- .../src/proprietary/ui}/ChatFABWindow.tsx | 2 +- .../src/proprietary/ui}/Checkbox.css | 0 .../src/proprietary/ui}/Checkbox.tsx | 2 +- .../src/proprietary/ui}/Chip.css | 0 .../src/proprietary/ui}/Chip.stories.tsx | 2 +- .../src/proprietary/ui}/Chip.tsx | 2 +- .../src/proprietary/ui}/ChipFlow.css | 0 .../src/proprietary/ui}/ChipFlow.stories.tsx | 2 +- .../src/proprietary/ui}/ChipFlow.tsx | 6 +- .../src/proprietary/ui}/CodeBlock.css | 0 .../src/proprietary/ui}/CodeBlock.stories.tsx | 2 +- .../src/proprietary/ui}/CodeBlock.tsx | 2 +- .../src/proprietary/ui}/Collapsible.css | 0 .../proprietary/ui}/Collapsible.stories.tsx | 2 +- .../src/proprietary/ui}/Collapsible.tsx | 2 +- .../src/proprietary/ui}/DataRow.css | 0 .../src/proprietary/ui}/DataRow.stories.tsx | 6 +- .../src/proprietary/ui}/DataRow.tsx | 2 +- .../src/proprietary/ui}/Drawer.css | 0 .../src/proprietary/ui}/Drawer.stories.tsx | 4 +- .../src/proprietary/ui}/Drawer.tsx | 2 +- .../src/proprietary/ui}/Dropdown.css | 0 .../src/proprietary/ui}/Dropdown.stories.tsx | 4 +- .../src/proprietary/ui}/Dropdown.tsx | 2 +- .../src/proprietary/ui}/EmptyState.css | 0 .../proprietary/ui}/EmptyState.stories.tsx | 6 +- .../src/proprietary/ui}/EmptyState.tsx | 2 +- .../src/proprietary/ui}/FormField.css | 0 .../src/proprietary/ui}/FormField.tsx | 2 +- .../src/proprietary/ui}/Forms.stories.tsx | 16 +- .../src/proprietary/ui}/IconBadge.css | 0 .../src/proprietary/ui}/IconBadge.stories.tsx | 2 +- .../src/proprietary/ui}/IconBadge.tsx | 2 +- .../src/proprietary/ui}/Inline.css | 0 .../src/proprietary/ui}/Inline.stories.tsx | 6 +- .../src/proprietary/ui}/Inline.tsx | 8 +- .../src/proprietary/ui}/Input.css | 0 .../src/proprietary/ui}/Input.tsx | 2 +- .../src/proprietary/ui}/ListRow.css | 0 .../src/proprietary/ui}/ListRow.stories.tsx | 6 +- .../src/proprietary/ui}/ListRow.tsx | 4 +- .../src/proprietary/ui}/MethodBadge.css | 0 .../proprietary/ui}/MethodBadge.stories.tsx | 2 +- .../src/proprietary/ui}/MethodBadge.tsx | 2 +- .../src/proprietary/ui}/MetricCard.css | 0 .../proprietary/ui}/MetricCard.stories.tsx | 2 +- .../src/proprietary/ui}/MetricCard.tsx | 2 +- .../src/proprietary/ui}/MetricStrip.css | 0 .../proprietary/ui}/MetricStrip.stories.tsx | 4 +- .../src/proprietary/ui}/MetricStrip.tsx | 2 +- .../src/proprietary/ui}/Modal.css | 3 + .../src/proprietary/ui}/Modal.tsx | 2 +- .../src/proprietary/ui}/NavItem.css | 0 .../src/proprietary/ui}/NavItem.stories.tsx | 4 +- .../src/proprietary/ui}/NavItem.tsx | 2 +- .../src/proprietary/ui}/PanelHeader.css | 0 .../proprietary/ui}/PanelHeader.stories.tsx | 4 +- .../src/proprietary/ui}/PanelHeader.tsx | 4 +- .../src/proprietary/ui}/ProgressBar.css | 0 .../proprietary/ui}/ProgressBar.stories.tsx | 2 +- .../src/proprietary/ui}/ProgressBar.tsx | 2 +- .../src/proprietary/ui}/Radio.css | 0 .../src/proprietary/ui}/Radio.tsx | 2 +- .../src/proprietary/ui}/SectionDivider.css | 0 .../ui}/SectionDivider.stories.tsx | 2 +- .../src/proprietary/ui}/SectionDivider.tsx | 2 +- .../src/proprietary/ui}/SectionHeader.css | 0 .../proprietary/ui}/SectionHeader.stories.tsx | 2 +- .../src/proprietary/ui}/SectionHeader.tsx | 2 +- .../src/proprietary/ui}/Select.css | 0 .../src/proprietary/ui}/Select.tsx | 2 +- .../src/proprietary/ui}/SettingsRow.css | 0 .../proprietary/ui}/SettingsRow.stories.tsx | 8 +- .../src/proprietary/ui}/SettingsRow.tsx | 2 +- .../src/proprietary/ui}/SettingsShell.css | 0 .../proprietary/ui}/SettingsShell.stories.tsx | 7 +- .../src/proprietary/ui}/SettingsShell.tsx | 2 +- .../src/proprietary/ui}/Skeleton.css | 0 .../src/proprietary/ui}/Skeleton.stories.tsx | 2 +- .../src/proprietary/ui}/Skeleton.tsx | 2 +- .../src/proprietary/ui}/Slider.css | 0 .../src/proprietary/ui}/Slider.tsx | 2 +- .../src/proprietary/ui}/Spinner.css | 0 .../src/proprietary/ui}/Spinner.stories.tsx | 2 +- .../src/proprietary/ui}/Spinner.tsx | 2 +- .../src/proprietary/ui}/Stack.css | 0 .../src/proprietary/ui}/Stack.stories.tsx | 6 +- .../src/proprietary/ui}/Stack.tsx | 2 +- .../src/proprietary/ui}/StatTile.css | 0 .../src/proprietary/ui}/StatTile.stories.tsx | 2 +- .../src/proprietary/ui}/StatTile.tsx | 2 +- .../src/proprietary/ui}/StatusBadge.css | 0 .../proprietary/ui}/StatusBadge.stories.tsx | 2 +- .../src/proprietary/ui}/StatusBadge.tsx | 2 +- .../src/proprietary/ui}/StepIndicator.css | 0 .../proprietary/ui}/StepIndicator.stories.tsx | 2 +- .../src/proprietary/ui}/StepIndicator.tsx | 2 +- .../src/proprietary/ui}/Table.css | 0 .../src/proprietary/ui}/Table.stories.tsx | 4 +- .../src/proprietary/ui}/Table.tsx | 2 +- .../src/proprietary/ui}/Tabs.css | 0 .../src/proprietary/ui}/Tabs.stories.tsx | 2 +- .../src/proprietary/ui}/Tabs.tsx | 2 +- .../src/proprietary/ui}/Toast.css | 0 .../src/proprietary/ui}/Toast.stories.tsx | 4 +- .../src/proprietary/ui}/Toast.tsx | 2 +- .../src/proprietary/ui}/ToggleSwitch.css | 0 .../proprietary/ui}/ToggleSwitch.stories.tsx | 2 +- .../src/proprietary/ui}/ToggleSwitch.tsx | 2 +- frontend/editor/src/proprietary/ui/index.ts | 43 + frontend/editor/src/prototypes/App.tsx | 2 +- frontend/editor/src/prototypes/tsconfig.json | 3 +- frontend/editor/src/saas/App.tsx | 5 + .../shared/config/configSections/Overview.tsx | 2 +- frontend/editor/src/saas/routes/Login.tsx | 6 +- .../editor/src/saas/routes/OAuthConsent.tsx | 6 +- .../editor/src/saas/routes/ResetPassword.tsx | 2 +- frontend/editor/src/saas/routes/Signup.tsx | 6 +- .../src/saas/routes/authShared/AuthLayout.tsx | 2 +- .../routes/authShared/GuestSignInButton.tsx | 2 +- .../saas/routes/login/EmailPasswordForm.tsx | 2 +- .../src/saas/routes/login/MagicLinkForm.tsx | 2 +- .../src/saas/routes/login/OAuthButtons.tsx | 2 +- frontend/editor/src/saas/tsconfig.json | 4 +- frontend/editor/tsconfig.core.vite.json | 3 +- frontend/editor/tsconfig.desktop.vite.json | 3 +- frontend/editor/tsconfig.json | 3 +- frontend/editor/tsconfig.portal.vite.json | 18 + .../editor/tsconfig.proprietary.vite.json | 4 +- frontend/editor/tsconfig.prototypes.vite.json | 3 +- frontend/editor/tsconfig.saas.vite.json | 4 +- frontend/editor/vite.config.ts | 19 +- frontend/editor/vitest.config.ts | 40 +- frontend/eslint.config.mjs | 50 +- frontend/package.json | 2 +- frontend/portal/.env | 31 - frontend/portal/index.html | 16 - frontend/portal/main.tsx | 38 - .../public/locales/en-US/translation.toml | 1895 ----------------- frontend/portal/src/ViewRouter.tsx | 40 - frontend/portal/src/i18n/config.ts | 54 - frontend/portal/src/mocks/browser.ts | 41 - frontend/portal/tsconfig.json | 18 - frontend/portal/vite.config.ts | 84 - frontend/portal/vitest.config.ts | 45 - frontend/scripts/dev-origin-proxy.ts | 246 --- frontend/scripts/tsconfig.json | 15 - .../assets/brand/modern-logo/favicon.ico | Bin 15406 -> 0 bytes frontend/shared/components/index.ts | 43 - frontend/shared/tsconfig.json | 17 - frontend/shared/vite-env.d.ts | 13 - 689 files changed, 4509 insertions(+), 4783 deletions(-) create mode 100644 frontend/editor/.env.proprietary rename frontend/{portal => editor}/public/mockServiceWorker.js (100%) rename frontend/{shared => editor/src/core}/assets/Brand.stories.tsx (75%) rename frontend/{shared => editor/src/core}/assets/brand/classic-logo/Firstpage.png (100%) rename frontend/{shared => editor/src/core}/assets/brand/classic-logo/StirlingPDFLogoBlackText.svg (100%) rename frontend/{shared => editor/src/core}/assets/brand/classic-logo/StirlingPDFLogoGreyText.svg (100%) rename frontend/{shared => editor/src/core}/assets/brand/classic-logo/StirlingPDFLogoNoTextDark.svg (100%) rename frontend/{shared => editor/src/core}/assets/brand/classic-logo/StirlingPDFLogoNoTextLight.svg (100%) rename frontend/{shared => editor/src/core}/assets/brand/classic-logo/StirlingPDFLogoWhiteText.svg (100%) rename frontend/{shared => editor/src/core}/assets/brand/classic-logo/favicon.ico (100%) rename frontend/{shared => editor/src/core}/assets/brand/classic-logo/logo-tooltip.svg (100%) rename frontend/{shared => editor/src/core}/assets/brand/classic-logo/logo192.png (100%) rename frontend/{shared => editor/src/core}/assets/brand/classic-logo/logo512.png (100%) rename frontend/{shared => editor/src/core}/assets/brand/modern-logo/Firstpage.png (100%) rename frontend/{shared => editor/src/core}/assets/brand/modern-logo/LoginDarkModeHeader.svg (100%) rename frontend/{shared => editor/src/core}/assets/brand/modern-logo/LoginLightModeHeader.svg (100%) rename frontend/{shared => editor/src/core}/assets/brand/modern-logo/StirlingPDFLogoBlackText.svg (100%) rename frontend/{shared => editor/src/core}/assets/brand/modern-logo/StirlingPDFLogoGreyText.svg (100%) rename frontend/{shared => editor/src/core}/assets/brand/modern-logo/StirlingPDFLogoNoTextDark.svg (100%) rename frontend/{shared => editor/src/core}/assets/brand/modern-logo/StirlingPDFLogoNoTextLight.svg (100%) rename frontend/{shared => editor/src/core}/assets/brand/modern-logo/StirlingPDFLogoWhiteText.svg (100%) rename frontend/{portal/public => editor/src/core/assets/brand/modern-logo}/favicon.ico (100%) rename frontend/{shared => editor/src/core}/assets/brand/modern-logo/logo-tooltip.svg (100%) rename frontend/{shared => editor/src/core}/assets/brand/modern-logo/logo192.png (100%) rename frontend/{shared => editor/src/core}/assets/brand/modern-logo/logo512.png (100%) rename frontend/{shared => editor/src/core}/assets/login/AddToPDF.png (100%) rename frontend/{shared => editor/src/core}/assets/login/Firstpage.png (100%) rename frontend/{shared => editor/src/core}/assets/login/LoginBackgroundPanel.png (100%) rename frontend/{shared => editor/src/core}/assets/login/SecurePDF.png (100%) rename frontend/{shared => editor/src/core}/assets/login/apple.svg (100%) rename frontend/{shared => editor/src/core}/assets/login/authentik.svg (100%) rename frontend/{shared => editor/src/core}/assets/login/cloudron.svg (100%) rename frontend/{shared => editor/src/core}/assets/login/github.svg (100%) rename frontend/{shared => editor/src/core}/assets/login/google.svg (100%) rename frontend/{shared => editor/src/core}/assets/login/keycloak.svg (100%) rename frontend/{shared => editor/src/core}/assets/login/microsoft.svg (100%) rename frontend/{shared => editor/src/core}/assets/login/oidc.svg (100%) rename frontend/{shared => editor/src/core}/auth/ui/oauthIcons.ts (67%) create mode 100644 frontend/editor/src/core/components/shared/ToolPanelHeader.css create mode 100644 frontend/editor/src/core/components/shared/ToolPanelHeader.tsx rename frontend/{shared => editor/src/core}/i18n/languages.ts (100%) rename frontend/{shared => editor/src/core}/i18n/tomlBackend.ts (100%) rename frontend/{shared => editor/src/core}/i18n/translationAudit.ts (90%) create mode 100644 frontend/editor/src/core/routes/adminRouteExtensions.tsx rename frontend/{shared => editor/src/core}/tokens/Tokens.stories.tsx (100%) rename frontend/{shared => editor/src/core}/tokens/base.css (100%) rename frontend/{shared => editor/src/core}/tokens/tokens.css (100%) rename frontend/{shared => editor/src/core}/tokens/tokens.ts (100%) create mode 100644 frontend/editor/src/desktop/routes/adminRouteExtensions.tsx rename frontend/{ => editor/src}/portal/LICENSE (100%) rename frontend/{ => editor/src}/portal/MOCKS.md (100%) rename frontend/{portal/src/App.tsx => editor/src/portal/PortalApp.tsx} (81%) rename frontend/{ => editor/src}/portal/UI_CONVENTIONS.md (95%) create mode 100644 frontend/editor/src/portal/ViewRouter.tsx rename frontend/{portal/src => editor/src/portal}/api/agents.ts (100%) rename frontend/{portal/src => editor/src/portal}/api/assistant.ts (100%) rename frontend/{portal/src => editor/src/portal}/api/billing.ts (95%) rename frontend/{portal/src => editor/src/portal}/api/docs.ts (100%) rename frontend/{portal/src => editor/src/portal}/api/documents.ts (100%) rename frontend/{portal/src => editor/src/portal}/api/editorDeploy.ts (100%) rename frontend/{portal/src => editor/src/portal}/api/home.ts (100%) rename frontend/{portal/src => editor/src/portal}/api/http.test.ts (96%) rename frontend/{portal/src => editor/src/portal}/api/http.ts (97%) rename frontend/{portal/src => editor/src/portal}/api/infrastructure.ts (100%) rename frontend/{portal/src => editor/src/portal}/api/link.test.ts (98%) rename frontend/{portal/src => editor/src/portal}/api/link.ts (100%) rename frontend/{portal/src => editor/src/portal}/api/notifications.ts (100%) rename frontend/{portal/src => editor/src/portal}/api/ops.ts (100%) rename frontend/{portal/src => editor/src/portal}/api/pipelines.ts (100%) rename frontend/{portal/src => editor/src/portal}/api/policies.ts (95%) rename frontend/{portal/src => editor/src/portal}/api/procurement.ts (100%) rename frontend/{portal/src => editor/src/portal}/api/sdkComponents.ts (100%) rename frontend/{portal/src => editor/src/portal}/api/search.ts (100%) rename frontend/{portal/src => editor/src/portal}/api/settings.ts (100%) rename frontend/{portal/src => editor/src/portal}/api/sources.ts (100%) rename frontend/{portal/src => editor/src/portal}/api/users.ts (100%) rename frontend/{portal/src => editor/src/portal}/auth/editorUrl.ts (86%) rename frontend/{portal/src => editor/src/portal}/auth/saasSupabase.ts (97%) rename frontend/{portal/src => editor/src/portal}/auth/saasSupabaseLogin.test.ts (96%) rename frontend/{portal/src => editor/src/portal}/billing/sharedBillingFormat.test.ts (96%) rename frontend/{portal/src => editor/src/portal}/billing/stripe.test.ts (98%) rename frontend/{portal/src => editor/src/portal}/billing/stripe.ts (98%) rename frontend/{portal/src => editor/src/portal}/components/AppShell.css (100%) rename frontend/{portal/src => editor/src/portal}/components/AppShell.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/AssistantButton.css (100%) rename frontend/{portal/src => editor/src/portal}/components/AssistantButton.tsx (87%) rename frontend/{portal/src => editor/src/portal}/components/AssistantPanel.css (100%) rename frontend/{portal/src => editor/src/portal}/components/AssistantPanel.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/AssistantPanel.tsx (89%) rename frontend/{portal/src => editor/src/portal}/components/AuthGate.tsx (83%) rename frontend/{portal/src => editor/src/portal}/components/ChatFABWidget.stories.tsx (98%) rename frontend/{portal/src => editor/src/portal}/components/ErrorBoundary.test.tsx (81%) rename frontend/{portal/src => editor/src/portal}/components/ErrorBoundary.tsx (88%) rename frontend/{portal/src => editor/src/portal}/components/Header.css (100%) rename frontend/{portal/src => editor/src/portal}/components/Header.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/Header.tsx (85%) rename frontend/{portal/src => editor/src/portal}/components/LoginScreen.tsx (68%) rename frontend/{portal/src => editor/src/portal}/components/MocksToggle.css (100%) rename frontend/{portal/src => editor/src/portal}/components/MocksToggle.tsx (89%) rename frontend/{portal/src => editor/src/portal}/components/NotificationsDropdown.css (100%) rename frontend/{portal/src => editor/src/portal}/components/NotificationsDropdown.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/NotificationsDropdown.tsx (85%) rename frontend/{portal/src => editor/src/portal}/components/PipelineForkWizard.css (100%) rename frontend/{portal/src => editor/src/portal}/components/PipelineForkWizard.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/PipelineForkWizard.tsx (90%) rename frontend/{portal/src => editor/src/portal}/components/PolicySummary.css (100%) rename frontend/{portal/src => editor/src/portal}/components/PolicySummary.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/PolicySummary.tsx (81%) rename frontend/{portal/src => editor/src/portal}/components/PopularUseCases.css (100%) rename frontend/{portal/src => editor/src/portal}/components/PopularUseCases.tsx (79%) rename frontend/{portal/src => editor/src/portal}/components/ProcessingStatusStrip.css (100%) rename frontend/{portal/src => editor/src/portal}/components/ProcessingStatusStrip.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/ProcessingStatusStrip.tsx (89%) rename frontend/{portal/src => editor/src/portal}/components/RecentActivity.css (100%) rename frontend/{portal/src => editor/src/portal}/components/RecentActivity.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/RecentActivity.tsx (87%) rename frontend/{portal/src => editor/src/portal}/components/SearchModal.css (100%) rename frontend/{portal/src => editor/src/portal}/components/SearchModal.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/SearchModal.tsx (88%) rename frontend/{portal/src => editor/src/portal}/components/SettingsModal.css (100%) rename frontend/{portal/src => editor/src/portal}/components/SettingsModal.tsx (85%) rename frontend/{portal/src => editor/src/portal}/components/Sidebar.css (100%) rename frontend/{portal/src => editor/src/portal}/components/Sidebar.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/Sidebar.tsx (88%) rename frontend/{portal/src => editor/src/portal}/components/SingleOpRunner.css (100%) rename frontend/{portal/src => editor/src/portal}/components/SingleOpRunner.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/SingleOpRunner.tsx (84%) rename frontend/{portal/src => editor/src/portal}/components/UsageAreaChart.css (100%) rename frontend/{portal/src => editor/src/portal}/components/UsageAreaChart.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/UsageAreaChart.tsx (97%) rename frontend/{portal/src => editor/src/portal}/components/WelcomeCarousel.css (100%) rename frontend/{portal/src => editor/src/portal}/components/WelcomeCarousel.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/WelcomeCarousel.tsx (82%) rename frontend/{portal/src => editor/src/portal}/components/account-link/AccountLinkPanel.tsx (90%) rename frontend/{portal/src => editor/src/portal}/components/account-link/LinkAccountCard.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/account-link/LinkAccountCard.tsx (72%) rename frontend/{portal/src => editor/src/portal}/components/account-link/LinkAccountModal.tsx (76%) rename frontend/{portal/src => editor/src/portal}/components/account-link/LinkGate.stories.tsx (93%) rename frontend/{portal/src => editor/src/portal}/components/account-link/LinkGate.tsx (74%) rename frontend/{portal/src => editor/src/portal}/components/account-link/LinkedInstancesTable.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/account-link/LinkedInstancesTable.tsx (69%) rename frontend/{portal/src => editor/src/portal}/components/agent-builder/AgentBuilderPanel.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/agent-builder/AgentBuilderPanel.tsx (87%) rename frontend/{portal/src => editor/src/portal}/components/agent-builder/AgentKpiStrip.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/agent-builder/AgentKpiStrip.tsx (70%) rename frontend/{portal/src => editor/src/portal}/components/agent-builder/AgentSelector.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/agent-builder/AgentSelector.tsx (93%) rename frontend/{portal/src => editor/src/portal}/components/agent-builder/BootstrapDialog.stories.tsx (95%) rename frontend/{portal/src => editor/src/portal}/components/agent-builder/BootstrapDialog.tsx (82%) rename frontend/{portal/src => editor/src/portal}/components/agent-builder/EvalsPanel.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/agent-builder/EvalsPanel.tsx (73%) rename frontend/{portal/src => editor/src/portal}/components/agent-builder/ScenariosPanel.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/agent-builder/ScenariosPanel.tsx (79%) rename frontend/{portal/src => editor/src/portal}/components/agent-builder/ToolsPanel.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/agent-builder/ToolsPanel.tsx (83%) rename frontend/{portal/src => editor/src/portal}/components/agent-builder/VersionsPanel.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/agent-builder/VersionsPanel.tsx (91%) rename frontend/{portal/src => editor/src/portal}/components/billing/EnterpriseUpsell.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/billing/EnterpriseUpsell.tsx (74%) rename frontend/{portal/src => editor/src/portal}/components/billing/FreePdfEditorsCard.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/billing/FreePdfEditorsCard.tsx (75%) rename frontend/{portal/src => editor/src/portal}/components/billing/FreePlanView.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/billing/FreePlanView.tsx (83%) rename frontend/{portal/src => editor/src/portal}/components/billing/InvoicesList.tsx (82%) rename frontend/{portal/src => editor/src/portal}/components/billing/LinkAccountPrompt.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/billing/LinkAccountPrompt.tsx (76%) rename frontend/{portal/src => editor/src/portal}/components/billing/PaymentMethodCard.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/billing/PaymentMethodCard.tsx (79%) rename frontend/{portal/src => editor/src/portal}/components/billing/PdfsProcessedCard.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/billing/PdfsProcessedCard.tsx (80%) rename frontend/{portal/src => editor/src/portal}/components/billing/SpendLimitCard.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/billing/SpendLimitCard.tsx (84%) rename frontend/{portal/src => editor/src/portal}/components/billing/SpendThisMonthCard.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/billing/SpendThisMonthCard.tsx (86%) rename frontend/{portal/src => editor/src/portal}/components/billing/StripeCheckoutModal.tsx (89%) rename frontend/{portal/src => editor/src/portal}/components/billing/SubscribedPlanView.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/billing/SubscribedPlanView.tsx (87%) rename frontend/{portal/src => editor/src/portal}/components/billing/WalletMeter.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/billing/WalletMeter.tsx (78%) rename frontend/{portal/src => editor/src/portal}/components/billing/billing.css (100%) rename frontend/{portal/src => editor/src/portal}/components/billing/walletFixtures.ts (100%) rename frontend/{portal/src => editor/src/portal}/components/catalogue/ComponentCard.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/catalogue/ComponentCard.tsx (90%) rename frontend/{portal/src => editor/src/portal}/components/catalogue/ComponentDetailModal.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/catalogue/ComponentDetailModal.tsx (79%) rename frontend/{portal/src => editor/src/portal}/components/catalogue/ComponentGrid.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/catalogue/ComponentGrid.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/catalogue/ComponentPropsTable.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/catalogue/ComponentPropsTable.tsx (77%) rename frontend/{portal/src => editor/src/portal}/components/catalogue/ComponentsSummaryStrip.tsx (82%) rename frontend/{portal/src => editor/src/portal}/components/docs/AuthenticationSection.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/docs/AuthenticationSection.tsx (65%) rename frontend/{portal/src => editor/src/portal}/components/docs/ComponentsSection.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/docs/ComponentsSection.tsx (81%) rename frontend/{portal/src => editor/src/portal}/components/docs/DocsNav.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/docs/DocsNav.tsx (93%) rename frontend/{portal/src => editor/src/portal}/components/docs/DocsSection.stories.tsx (95%) rename frontend/{portal/src => editor/src/portal}/components/docs/DocsSection.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/docs/EndpointReferenceSection.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/docs/EndpointReferenceSection.tsx (81%) rename frontend/{portal/src => editor/src/portal}/components/docs/ErrorsSection.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/docs/ErrorsSection.tsx (80%) rename frontend/{portal/src => editor/src/portal}/components/docs/GettingStartedSection.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/docs/GettingStartedSection.tsx (60%) rename frontend/{portal/src => editor/src/portal}/components/docs/LangSnippet.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/docs/LangSnippet.tsx (91%) rename frontend/{portal/src => editor/src/portal}/components/docs/PlaybooksSection.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/docs/PlaybooksSection.tsx (86%) rename frontend/{portal/src => editor/src/portal}/components/docs/RateLimitsSection.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/docs/RateLimitsSection.tsx (74%) rename frontend/{portal/src => editor/src/portal}/components/docs/SdksSection.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/docs/SdksSection.tsx (79%) rename frontend/{portal/src => editor/src/portal}/components/docs/SkillsSection.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/docs/SkillsSection.tsx (84%) rename frontend/{portal/src => editor/src/portal}/components/docs/WebhooksSection.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/docs/WebhooksSection.tsx (58%) rename frontend/{portal/src => editor/src/portal}/components/documents/DocumentAudit.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/documents/DocumentAudit.tsx (90%) rename frontend/{portal/src => editor/src/portal}/components/documents/DocumentDrawer.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/documents/DocumentDrawer.tsx (90%) rename frontend/{portal/src => editor/src/portal}/components/documents/DocumentExtractions.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/documents/DocumentExtractions.tsx (82%) rename frontend/{portal/src => editor/src/portal}/components/documents/DocumentOverview.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/documents/DocumentOverview.tsx (57%) rename frontend/{portal/src => editor/src/portal}/components/documents/DocumentsSummaryStrip.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/documents/DocumentsSummaryStrip.tsx (78%) rename frontend/{portal/src => editor/src/portal}/components/documents/ElevationBanner.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/documents/ElevationBanner.tsx (71%) rename frontend/{portal/src => editor/src/portal}/components/documents/ReviewQueue.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/documents/ReviewQueue.tsx (87%) rename frontend/{portal/src => editor/src/portal}/components/documents/ReviewQueueTable.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/documents/ReviewQueueTable.tsx (78%) rename frontend/{portal/src => editor/src/portal}/components/documents/format.ts (94%) rename frontend/{portal/src => editor/src/portal}/components/editor-admin/CredentialRotationCard.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/editor-admin/CredentialRotationCard.tsx (76%) rename frontend/{portal/src => editor/src/portal}/components/editor-admin/DeploymentSummaryStrip.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/editor-admin/DeploymentSummaryStrip.tsx (91%) rename frontend/{portal/src => editor/src/portal}/components/editor-admin/DeploymentTargets.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/editor-admin/DeploymentTargets.tsx (87%) rename frontend/{portal/src => editor/src/portal}/components/editor-admin/InstanceHealthTable.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/editor-admin/InstanceHealthTable.tsx (79%) rename frontend/{portal/src => editor/src/portal}/components/editor-admin/OfflineActivationCard.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/editor-admin/OfflineActivationCard.tsx (77%) rename frontend/{portal/src => editor/src/portal}/components/editor-admin/PairingPanel.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/editor-admin/PairingPanel.tsx (89%) rename frontend/{portal/src => editor/src/portal}/components/icons.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/infrastructure/ApiKeyCard.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/infrastructure/ApiKeyCard.tsx (80%) rename frontend/{portal/src => editor/src/portal}/components/infrastructure/ApiKeysTab.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/infrastructure/ApiKeysTab.tsx (82%) rename frontend/{portal/src => editor/src/portal}/components/infrastructure/AuditTab.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/infrastructure/AuditTab.tsx (66%) rename frontend/{portal/src => editor/src/portal}/components/infrastructure/CreateKeyModal.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/infrastructure/CreateKeyModal.tsx (75%) rename frontend/{portal/src => editor/src/portal}/components/infrastructure/DeploymentsTab.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/infrastructure/DeploymentsTab.tsx (72%) rename frontend/{portal/src => editor/src/portal}/components/infrastructure/ModelsTab.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/infrastructure/ModelsTab.tsx (69%) rename frontend/{portal/src => editor/src/portal}/components/infrastructure/SectionHeader.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/infrastructure/SectionHeader.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/infrastructure/SecurityTab.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/infrastructure/SecurityTab.tsx (68%) rename frontend/{portal/src => editor/src/portal}/components/infrastructure/StorageTab.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/infrastructure/StorageTab.tsx (72%) rename frontend/{portal/src => editor/src/portal}/components/infrastructure/TableSkeleton.stories.tsx (93%) rename frontend/{portal/src => editor/src/portal}/components/infrastructure/TableSkeleton.tsx (91%) rename frontend/{portal/src => editor/src/portal}/components/infrastructure/infraFormat.ts (98%) rename frontend/{portal/src => editor/src/portal}/components/pipelines/KpiStrip.tsx (87%) rename frontend/{portal/src => editor/src/portal}/components/pipelines/PipelineComposer.tsx (88%) rename frontend/{portal/src => editor/src/portal}/components/pipelines/PipelineDetailCard.tsx (82%) rename frontend/{portal/src => editor/src/portal}/components/pipelines/PipelinesTable.tsx (88%) rename frontend/{portal/src => editor/src/portal}/components/pipelines/pipelineOperations.ts (100%) rename frontend/{portal/src => editor/src/portal}/components/policies/CatalogueSummary.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/policies/CatalogueSummary.tsx (61%) rename frontend/{portal/src => editor/src/portal}/components/policies/PolicyCategoryCard.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/policies/PolicyCategoryCard.tsx (87%) rename frontend/{portal/src => editor/src/portal}/components/policies/PolicyDetailPanel.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/policies/PolicyDetailPanel.tsx (85%) rename frontend/{portal/src => editor/src/portal}/components/policies/PolicyFieldRow.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/policies/PolicyFieldRow.tsx (95%) rename frontend/{portal/src => editor/src/portal}/components/policies/PolicySetupWizard.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/policies/PolicySetupWizard.tsx (83%) rename frontend/{portal/src => editor/src/portal}/components/policies/policyIcons.ts (100%) rename frontend/{portal/src => editor/src/portal}/components/policies/storyFixtures.ts (93%) rename frontend/{portal/src => editor/src/portal}/components/procurement/ActionModal.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/procurement/ActionModal.tsx (76%) rename frontend/{portal/src => editor/src/portal}/components/procurement/DealJourney.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/procurement/DealJourney.tsx (83%) rename frontend/{portal/src => editor/src/portal}/components/procurement/DocRow.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/procurement/DocRow.tsx (92%) rename frontend/{portal/src => editor/src/portal}/components/procurement/DocumentLedger.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/procurement/DocumentLedger.tsx (89%) rename frontend/{portal/src => editor/src/portal}/components/procurement/LockedState.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/procurement/LockedState.tsx (76%) rename frontend/{portal/src => editor/src/portal}/components/procurement/StageStepper.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/procurement/StageStepper.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/procurement/format.ts (61%) rename frontend/{portal/src => editor/src/portal}/components/sources/ConnectWizard.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/sources/ConnectWizard.test.tsx (89%) rename frontend/{portal/src => editor/src/portal}/components/sources/ConnectWizard.tsx (89%) rename frontend/{portal/src => editor/src/portal}/components/sources/KpiStrip.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/sources/KpiStrip.tsx (87%) rename frontend/{portal/src => editor/src/portal}/components/sources/SourceDetailCard.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/sources/SourceDetailCard.tsx (84%) rename frontend/{portal/src => editor/src/portal}/components/sources/SourceDetailPanel.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/sources/SourceDetailPanel.tsx (83%) rename frontend/{portal/src => editor/src/portal}/components/sources/SourcesTable.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/sources/SourcesTable.tsx (92%) rename frontend/{portal/src => editor/src/portal}/components/sources/Sparkline.test.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/sources/Sparkline.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/sources/sourceTypes.ts (69%) rename frontend/{portal/src => editor/src/portal}/components/users/AccessControls.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/users/AccessControls.tsx (78%) rename frontend/{portal/src => editor/src/portal}/components/users/InviteMemberModal.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/users/InviteMemberModal.tsx (78%) rename frontend/{portal/src => editor/src/portal}/components/users/MembersTable.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/users/MembersTable.tsx (86%) rename frontend/{portal/src => editor/src/portal}/components/users/RolesGrid.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/users/RolesGrid.tsx (86%) rename frontend/{portal/src => editor/src/portal}/components/users/UsersSummaryStrip.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/components/users/UsersSummaryStrip.tsx (91%) rename frontend/{portal/src => editor/src/portal}/components/users/format.ts (91%) rename frontend/{portal/src => editor/src/portal}/contexts/AccountLinkContext.tsx (100%) rename frontend/{portal/src => editor/src/portal}/contexts/LinkContext.test.tsx (100%) rename frontend/{portal/src => editor/src/portal}/contexts/LinkContext.tsx (96%) rename frontend/{portal/src => editor/src/portal}/contexts/ThemeContext.tsx (100%) rename frontend/{portal/src => editor/src/portal}/contexts/TierContext.tsx (100%) rename frontend/{portal/src => editor/src/portal}/contexts/UIContext.tsx (100%) rename frontend/{portal/src => editor/src/portal}/contexts/ViewContext.tsx (69%) rename frontend/{shared => editor/src/portal}/data/Endpoints.stories.tsx (94%) rename frontend/{shared => editor/src/portal}/data/Ops.stories.tsx (99%) rename frontend/{shared => editor/src/portal}/data/endpoints.ts (99%) rename frontend/{shared => editor/src/portal}/data/ops.ts (100%) rename frontend/{portal/src => editor/src/portal}/hooks/useAccountLink.test.tsx (100%) rename frontend/{portal/src => editor/src/portal}/hooks/useAccountLink.ts (98%) rename frontend/{portal/src => editor/src/portal}/hooks/useAsync.ts (100%) rename frontend/{portal/src => editor/src/portal}/hooks/useStripePortal.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/agents.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/assistant.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/auth.ts (100%) create mode 100644 frontend/editor/src/portal/mocks/browser.ts rename frontend/{portal/src => editor/src/portal}/mocks/docs.ts (99%) rename frontend/{portal/src => editor/src/portal}/mocks/documents.ts (99%) rename frontend/{portal/src => editor/src/portal}/mocks/editorDeploy.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/handlers/agents.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/handlers/assistant.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/handlers/auth.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/handlers/docs.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/handlers/documents.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/handlers/editorDeploy.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/handlers/home.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/handlers/index.ts (66%) rename frontend/{portal/src => editor/src/portal}/mocks/handlers/infrastructure.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/handlers/link.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/handlers/notifications.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/handlers/ops.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/handlers/pipelines.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/handlers/policies.ts (98%) rename frontend/{portal/src => editor/src/portal}/mocks/handlers/procurement.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/handlers/sdkComponents.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/handlers/search.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/handlers/settings.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/handlers/sources.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/handlers/users.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/home.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/infrastructure.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/link.test.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/link.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/notifications.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/ops.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/policies.ts (97%) rename frontend/{portal/src => editor/src/portal}/mocks/preference.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/procurement.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/procurementMachine.test.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/procurementMachine.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/sampleDailySeries.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/sdkComponents.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/search.ts (100%) rename frontend/{portal/src => editor/src/portal}/mocks/settings.ts (100%) create mode 100644 frontend/editor/src/portal/mocks/startIfEnabled.ts rename frontend/{portal/src => editor/src/portal}/mocks/users.ts (100%) rename frontend/{portal/src => editor/src/portal}/setupTests.ts (100%) rename frontend/{portal/src => editor/src/portal}/theme/MantineIntegration.stories.tsx (96%) create mode 100644 frontend/editor/src/portal/theme/base.css rename frontend/{portal/src => editor/src/portal}/theme/mantineTheme.ts (100%) create mode 100644 frontend/editor/src/portal/tsconfig.json rename frontend/{portal/src => editor/src/portal}/views/AccountLink.css (100%) rename frontend/{portal/src => editor/src/portal}/views/AgentBuilder.css (100%) rename frontend/{portal/src => editor/src/portal}/views/AgentBuilder.tsx (85%) rename frontend/{portal/src => editor/src/portal}/views/Components.css (100%) rename frontend/{portal/src => editor/src/portal}/views/Components.tsx (85%) rename frontend/{portal/src => editor/src/portal}/views/DeveloperDocs.css (100%) rename frontend/{portal/src => editor/src/portal}/views/DeveloperDocs.tsx (95%) rename frontend/{portal/src => editor/src/portal}/views/Documents.css (100%) rename frontend/{portal/src => editor/src/portal}/views/Documents.tsx (74%) rename frontend/{portal/src => editor/src/portal}/views/EditorAdmin.css (100%) rename frontend/{portal/src => editor/src/portal}/views/EditorAdmin.tsx (84%) rename frontend/{portal/src => editor/src/portal}/views/Home.css (100%) rename frontend/{portal/src => editor/src/portal}/views/Home.stories.tsx (100%) rename frontend/{portal/src => editor/src/portal}/views/Home.tsx (85%) rename frontend/{portal/src => editor/src/portal}/views/Infrastructure.css (100%) rename frontend/{portal/src => editor/src/portal}/views/Infrastructure.tsx (67%) rename frontend/{portal/src => editor/src/portal}/views/Pipelines.css (100%) rename frontend/{portal/src => editor/src/portal}/views/Pipelines.test.tsx (90%) rename frontend/{portal/src => editor/src/portal}/views/Pipelines.tsx (86%) rename frontend/{portal/src => editor/src/portal}/views/Policies.css (100%) rename frontend/{portal/src => editor/src/portal}/views/Policies.tsx (92%) rename frontend/{portal/src => editor/src/portal}/views/Procurement.css (100%) rename frontend/{portal/src => editor/src/portal}/views/Procurement.tsx (90%) rename frontend/{portal/src => editor/src/portal}/views/Sources.css (100%) rename frontend/{portal/src => editor/src/portal}/views/Sources.test.tsx (94%) rename frontend/{portal/src => editor/src/portal}/views/Sources.tsx (89%) rename frontend/{portal/src => editor/src/portal}/views/Usage.css (100%) rename frontend/{portal/src => editor/src/portal}/views/Usage.tsx (90%) rename frontend/{portal/src => editor/src/portal}/views/Users.css (100%) rename frontend/{portal/src => editor/src/portal}/views/Users.tsx (88%) rename frontend/{portal/src => editor/src/portal}/vite-env.d.ts (96%) rename frontend/{shared => editor/src/proprietary}/auth/AuthProvider.tsx (85%) rename frontend/{shared => editor/src/proprietary}/auth/config.ts (93%) rename frontend/{shared => editor/src/proprietary}/auth/context.ts (92%) rename frontend/{shared => editor/src/proprietary}/auth/guards/RequireAdmin.tsx (96%) rename frontend/{shared => editor/src/proprietary}/auth/guards/RequireAuth.tsx (93%) rename frontend/{shared => editor/src/proprietary}/auth/guards/RequirePortalAccess.tsx (95%) rename frontend/{shared => editor/src/proprietary}/auth/httpClient.ts (100%) rename frontend/{shared => editor/src/proprietary}/auth/index.ts (64%) rename frontend/{shared => editor/src/proprietary}/auth/roles.ts (100%) rename frontend/{shared => editor/src/proprietary}/auth/spring/UseSession.tsx (95%) rename frontend/{shared => editor/src/proprietary}/auth/spring/oauthStorage.ts (100%) rename frontend/{shared => editor/src/proprietary}/auth/spring/oauthTypes.ts (100%) rename frontend/{shared => editor/src/proprietary}/auth/spring/platformBridge.ts (100%) rename frontend/{shared => editor/src/proprietary}/auth/spring/springAuthClient.ts (98%) rename frontend/{shared => editor/src/proprietary}/auth/supabase/UseSession.tsx (95%) rename frontend/{shared => editor/src/proprietary}/auth/supabase/supabaseClient.ts (100%) rename frontend/{shared => editor/src/proprietary}/auth/types.ts (100%) rename frontend/{shared => editor/src/proprietary}/auth/ui/AuthShell.module.css (100%) rename frontend/{shared => editor/src/proprietary}/auth/ui/AuthShell.tsx (97%) rename frontend/{shared => editor/src/proprietary}/auth/ui/EmailPasswordForm.tsx (99%) rename frontend/{shared => editor/src/proprietary}/auth/ui/ErrorMessage.tsx (100%) rename frontend/{shared => editor/src/proprietary}/auth/ui/LoginRightCarousel.tsx (98%) rename frontend/{shared => editor/src/proprietary}/auth/ui/OAuthButtons.tsx (98%) rename frontend/{shared => editor/src/proprietary}/auth/ui/SpringLoginForm.tsx (93%) rename frontend/{shared => editor/src/proprietary}/auth/ui/SupabaseLoginForm.tsx (88%) rename frontend/{shared => editor/src/proprietary}/auth/ui/auth-theme.css (100%) rename frontend/{shared => editor/src/proprietary}/auth/ui/auth.css (100%) rename frontend/{shared => editor/src/proprietary}/auth/ui/loginSlideText.ts (96%) rename frontend/{shared => editor/src/proprietary}/auth/ui/loginSlides.ts (55%) rename frontend/{shared => editor/src/proprietary}/auth/ui/useSpringLogin.ts (97%) rename frontend/{shared => editor/src/proprietary}/auth/ui/useSupabaseLogin.ts (96%) rename frontend/{shared => editor/src/proprietary}/billing/MeterBar.tsx (97%) rename frontend/{shared => editor/src/proprietary}/billing/SpendCapControl.tsx (98%) rename frontend/{shared => editor/src/proprietary}/billing/format.ts (100%) rename frontend/{shared => editor/src/proprietary}/billing/index.ts (69%) rename frontend/{shared => editor/src/proprietary}/billing/types.ts (100%) rename frontend/{shared => editor/src/proprietary}/policies/codec.test.ts (96%) rename frontend/{shared => editor/src/proprietary}/policies/codec.ts (98%) rename frontend/{shared => editor/src/proprietary}/policies/runs.test.ts (97%) rename frontend/{shared => editor/src/proprietary}/policies/runs.ts (98%) rename frontend/{shared => editor/src/proprietary}/policies/types.ts (100%) create mode 100644 frontend/editor/src/proprietary/routes/adminRouteExtensions.tsx rename frontend/{shared/components => editor/src/proprietary/ui}/Avatar.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/Avatar.stories.tsx (96%) rename frontend/{shared/components => editor/src/proprietary/ui}/Avatar.tsx (97%) rename frontend/{shared/components => editor/src/proprietary/ui}/Banner.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/Banner.stories.tsx (94%) rename frontend/{shared/components => editor/src/proprietary/ui}/Banner.tsx (97%) rename frontend/{shared/components => editor/src/proprietary/ui}/Button.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/Button.stories.tsx (96%) rename frontend/{shared/components => editor/src/proprietary/ui}/Button.tsx (98%) rename frontend/{shared/components => editor/src/proprietary/ui}/Card.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/Card.stories.tsx (95%) rename frontend/{shared/components => editor/src/proprietary/ui}/Card.tsx (97%) rename frontend/{shared/components => editor/src/proprietary/ui}/ChatFABButton.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/ChatFABButton.stories.tsx (94%) rename frontend/{shared/components => editor/src/proprietary/ui}/ChatFABButton.tsx (97%) rename frontend/{shared/components => editor/src/proprietary/ui}/ChatFABWindow.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/ChatFABWindow.stories.tsx (98%) rename frontend/{shared/components => editor/src/proprietary/ui}/ChatFABWindow.tsx (95%) rename frontend/{shared/components => editor/src/proprietary/ui}/Checkbox.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/Checkbox.tsx (98%) rename frontend/{shared/components => editor/src/proprietary/ui}/Chip.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/Chip.stories.tsx (97%) rename frontend/{shared/components => editor/src/proprietary/ui}/Chip.tsx (98%) rename frontend/{shared/components => editor/src/proprietary/ui}/ChipFlow.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/ChipFlow.stories.tsx (92%) rename frontend/{shared/components => editor/src/proprietary/ui}/ChipFlow.tsx (87%) rename frontend/{shared/components => editor/src/proprietary/ui}/CodeBlock.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/CodeBlock.stories.tsx (97%) rename frontend/{shared/components => editor/src/proprietary/ui}/CodeBlock.tsx (98%) rename frontend/{shared/components => editor/src/proprietary/ui}/Collapsible.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/Collapsible.stories.tsx (96%) rename frontend/{shared/components => editor/src/proprietary/ui}/Collapsible.tsx (97%) rename frontend/{shared/components => editor/src/proprietary/ui}/DataRow.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/DataRow.stories.tsx (85%) rename frontend/{shared/components => editor/src/proprietary/ui}/DataRow.tsx (96%) rename frontend/{shared/components => editor/src/proprietary/ui}/Drawer.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/Drawer.stories.tsx (95%) rename frontend/{shared/components => editor/src/proprietary/ui}/Drawer.tsx (98%) rename frontend/{shared/components => editor/src/proprietary/ui}/Dropdown.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/Dropdown.stories.tsx (95%) rename frontend/{shared/components => editor/src/proprietary/ui}/Dropdown.tsx (99%) rename frontend/{shared/components => editor/src/proprietary/ui}/EmptyState.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/EmptyState.stories.tsx (89%) rename frontend/{shared/components => editor/src/proprietary/ui}/EmptyState.tsx (96%) rename frontend/{shared/components => editor/src/proprietary/ui}/FormField.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/FormField.tsx (98%) rename frontend/{shared/components => editor/src/proprietary/ui}/Forms.stories.tsx (94%) rename frontend/{shared/components => editor/src/proprietary/ui}/IconBadge.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/IconBadge.stories.tsx (95%) rename frontend/{shared/components => editor/src/proprietary/ui}/IconBadge.tsx (95%) rename frontend/{shared/components => editor/src/proprietary/ui}/Inline.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/Inline.stories.tsx (87%) rename frontend/{shared/components => editor/src/proprietary/ui}/Inline.tsx (89%) rename frontend/{shared/components => editor/src/proprietary/ui}/Input.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/Input.tsx (97%) rename frontend/{shared/components => editor/src/proprietary/ui}/ListRow.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/ListRow.stories.tsx (92%) rename frontend/{shared/components => editor/src/proprietary/ui}/ListRow.tsx (95%) rename frontend/{shared/components => editor/src/proprietary/ui}/MethodBadge.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/MethodBadge.stories.tsx (92%) rename frontend/{shared/components => editor/src/proprietary/ui}/MethodBadge.tsx (93%) rename frontend/{shared/components => editor/src/proprietary/ui}/MetricCard.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/MetricCard.stories.tsx (97%) rename frontend/{shared/components => editor/src/proprietary/ui}/MetricCard.tsx (98%) rename frontend/{shared/components => editor/src/proprietary/ui}/MetricStrip.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/MetricStrip.stories.tsx (85%) rename frontend/{shared/components => editor/src/proprietary/ui}/MetricStrip.tsx (92%) rename frontend/{shared/components => editor/src/proprietary/ui}/Modal.css (91%) rename frontend/{shared/components => editor/src/proprietary/ui}/Modal.tsx (99%) rename frontend/{shared/components => editor/src/proprietary/ui}/NavItem.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/NavItem.stories.tsx (95%) rename frontend/{shared/components => editor/src/proprietary/ui}/NavItem.tsx (97%) rename frontend/{shared/components => editor/src/proprietary/ui}/PanelHeader.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/PanelHeader.stories.tsx (93%) rename frontend/{shared/components => editor/src/proprietary/ui}/PanelHeader.tsx (97%) rename frontend/{shared/components => editor/src/proprietary/ui}/ProgressBar.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/ProgressBar.stories.tsx (97%) rename frontend/{shared/components => editor/src/proprietary/ui}/ProgressBar.tsx (98%) rename frontend/{shared/components => editor/src/proprietary/ui}/Radio.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/Radio.tsx (98%) rename frontend/{shared/components => editor/src/proprietary/ui}/SectionDivider.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/SectionDivider.stories.tsx (95%) rename frontend/{shared/components => editor/src/proprietary/ui}/SectionDivider.tsx (92%) rename frontend/{shared/components => editor/src/proprietary/ui}/SectionHeader.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/SectionHeader.stories.tsx (92%) rename frontend/{shared/components => editor/src/proprietary/ui}/SectionHeader.tsx (97%) rename frontend/{shared/components => editor/src/proprietary/ui}/Select.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/Select.tsx (97%) rename frontend/{shared/components => editor/src/proprietary/ui}/SettingsRow.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/SettingsRow.stories.tsx (89%) rename frontend/{shared/components => editor/src/proprietary/ui}/SettingsRow.tsx (96%) rename frontend/{shared/components => editor/src/proprietary/ui}/SettingsShell.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/SettingsShell.stories.tsx (92%) rename frontend/{shared/components => editor/src/proprietary/ui}/SettingsShell.tsx (98%) rename frontend/{shared/components => editor/src/proprietary/ui}/Skeleton.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/Skeleton.stories.tsx (96%) rename frontend/{shared/components => editor/src/proprietary/ui}/Skeleton.tsx (97%) rename frontend/{shared/components => editor/src/proprietary/ui}/Slider.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/Slider.tsx (97%) rename frontend/{shared/components => editor/src/proprietary/ui}/Spinner.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/Spinner.stories.tsx (95%) rename frontend/{shared/components => editor/src/proprietary/ui}/Spinner.tsx (94%) rename frontend/{shared/components => editor/src/proprietary/ui}/Stack.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/Stack.stories.tsx (91%) rename frontend/{shared/components => editor/src/proprietary/ui}/Stack.tsx (97%) rename frontend/{shared/components => editor/src/proprietary/ui}/StatTile.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/StatTile.stories.tsx (93%) rename frontend/{shared/components => editor/src/proprietary/ui}/StatTile.tsx (95%) rename frontend/{shared/components => editor/src/proprietary/ui}/StatusBadge.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/StatusBadge.stories.tsx (96%) rename frontend/{shared/components => editor/src/proprietary/ui}/StatusBadge.tsx (96%) rename frontend/{shared/components => editor/src/proprietary/ui}/StepIndicator.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/StepIndicator.stories.tsx (93%) rename frontend/{shared/components => editor/src/proprietary/ui}/StepIndicator.tsx (95%) rename frontend/{shared/components => editor/src/proprietary/ui}/Table.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/Table.stories.tsx (94%) rename frontend/{shared/components => editor/src/proprietary/ui}/Table.tsx (98%) rename frontend/{shared/components => editor/src/proprietary/ui}/Tabs.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/Tabs.stories.tsx (97%) rename frontend/{shared/components => editor/src/proprietary/ui}/Tabs.tsx (98%) rename frontend/{shared/components => editor/src/proprietary/ui}/Toast.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/Toast.stories.tsx (91%) rename frontend/{shared/components => editor/src/proprietary/ui}/Toast.tsx (98%) rename frontend/{shared/components => editor/src/proprietary/ui}/ToggleSwitch.css (100%) rename frontend/{shared/components => editor/src/proprietary/ui}/ToggleSwitch.stories.tsx (97%) rename frontend/{shared/components => editor/src/proprietary/ui}/ToggleSwitch.tsx (97%) create mode 100644 frontend/editor/src/proprietary/ui/index.ts create mode 100644 frontend/editor/tsconfig.portal.vite.json delete mode 100644 frontend/portal/.env delete mode 100644 frontend/portal/index.html delete mode 100644 frontend/portal/main.tsx delete mode 100644 frontend/portal/public/locales/en-US/translation.toml delete mode 100644 frontend/portal/src/ViewRouter.tsx delete mode 100644 frontend/portal/src/i18n/config.ts delete mode 100644 frontend/portal/src/mocks/browser.ts delete mode 100644 frontend/portal/tsconfig.json delete mode 100644 frontend/portal/vite.config.ts delete mode 100644 frontend/portal/vitest.config.ts delete mode 100644 frontend/scripts/dev-origin-proxy.ts delete mode 100644 frontend/scripts/tsconfig.json delete mode 100644 frontend/shared/assets/brand/modern-logo/favicon.ico delete mode 100644 frontend/shared/components/index.ts delete mode 100644 frontend/shared/tsconfig.json delete mode 100644 frontend/shared/vite-env.d.ts diff --git a/.dockerignore b/.dockerignore index 33be4b6ee1..492480e3a0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -27,7 +27,6 @@ node_modules/ **/node_modules/ frontend/node_modules/ frontend/editor/dist/ -frontend/dist-portal/ frontend/editor/playwright-report/ .npm/ .yarn/ diff --git a/.gitleaksignore b/.gitleaksignore index 783a098f68..089de4fedb 100644 --- a/.gitleaksignore +++ b/.gitleaksignore @@ -15,10 +15,10 @@ testing/compose/validate-mcp-test.sh:curl-auth-header:92 testing/compose/validate-mcp-test.sh:curl-auth-header:116 # Storybook example showing curl with a fake Bearer token placeholder (sk_live_a3f8...). -frontend/shared/components/CodeBlock.stories.tsx:curl-auth-header:4 +frontend/editor/src/proprietary/ui/CodeBlock.stories.tsx:curl-auth-header:5 # Truncated placeholder API key in portal docs example (sk_live_8f2c...e10) - not a real secret. -frontend/portal/src/components/docs/GettingStartedSection.tsx:generic-api-key:31 +frontend/editor/src/portal/components/docs/GettingStartedSection.tsx:generic-api-key:30 # False positive: generic-api-key matches the Java type name "X509Certificate" # in a method signature (CreateSignatureBase.resolveSignatureAlgorithm) - not a secret. diff --git a/.taskfiles/frontend.yml b/.taskfiles/frontend.yml index 3ad0486759..52277b5b04 100644 --- a/.taskfiles/frontend.yml +++ b/.taskfiles/frontend.yml @@ -128,37 +128,6 @@ tasks: - task: dev:_run vars: { MODE: prototypes, PORT: '{{.PORT}}', BACKEND_URL: '{{.BACKEND_URL}}', OPEN: '{{.OPEN}}' } - dev:portal: - desc: "Start developer portal dev server" - ignore_error: true - deps: [install] - vars: - PORT: '{{.PORT | default "5173"}}' - BACKEND_URL: '{{.BACKEND_URL | default "http://localhost:8080"}}' - EDITOR_URL: '{{.EDITOR_URL | default ""}}' - OPEN: '{{.OPEN | default ""}}' - SUBPATH: '{{.SUBPATH | default ""}}' - MOCKS: '{{.MOCKS | default ""}}' - env: - BACKEND_URL: '{{.BACKEND_URL}}' - cmds: - - '{{if .SUBPATH}}RUN_SUBPATH={{.SUBPATH}} {{end}}{{if .MOCKS}}VITE_PORTAL_MOCKS={{.MOCKS}} {{end}}{{if .EDITOR_URL}}VITE_EDITOR_URL={{.EDITOR_URL}} {{end}}npx vite portal --port {{.PORT}}{{if .OPEN}} --open{{end}}' - - dev:portal:proxy:serve: - internal: true - vars: - PORT: '{{.PORT | default "3000"}}' - BACKEND_URL: '{{.BACKEND_URL | default "http://localhost:8080"}}' - EDITOR_DEV_URL: '{{.EDITOR_DEV_URL | default ""}}' - PORTAL_DEV_URL: '{{.PORTAL_DEV_URL | default ""}}' - env: - PORT: '{{.PORT}}' - BACKEND_URL: '{{.BACKEND_URL}}' - EDITOR_DEV_URL: '{{.EDITOR_DEV_URL}}' - PORTAL_DEV_URL: '{{.PORTAL_DEV_URL}}' - cmds: - - npx tsx scripts/dev-origin-proxy.ts - # ============================================================ # Build # ============================================================ @@ -205,29 +174,6 @@ tasks: cmds: - npx vite build editor --mode prototypes - build:portal: - desc: "Build developer portal" - deps: [install] - vars: - SUBPATH: '{{.SUBPATH | default ""}}' - cmds: - - '{{if .SUBPATH}}RUN_SUBPATH={{.SUBPATH}} {{end}}npx vite build portal' - - preview:portal:proxy: - desc: "Build + serve editor + portal behind one origin (prod-like auth testing)" - deps: [prepare] - vars: - PORT: '{{.PORT | default "3000"}}' - BACKEND_URL: '{{.BACKEND_URL | default "http://localhost:8080"}}' - env: - PORT: '{{.PORT}}' - BACKEND_URL: '{{.BACKEND_URL}}' - cmds: - - task: build:proprietary - vars: { PREVIEW: '1' } - - task: build:portal - vars: { SUBPATH: portal } - - npx tsx scripts/dev-origin-proxy.ts storybook: desc: "Start Storybook dev server" @@ -263,8 +209,8 @@ tasks: deps: [install] cmds: # Globs so dpdm walks the whole tree. dpdm expands the braces itself, so this is - # shell-agnostic. Covers editor, portal, and the shared design system. - - npx dpdm "editor/src/**/*.{ts,tsx}" "portal/src/**/*.{ts,tsx}" "shared/**/*.{ts,tsx}" --circular --no-warning --no-tree --exit-code circular:1 + # shell-agnostic. Covers the whole editor tree, including the portal layer. + - npx dpdm "editor/src/**/*.{ts,tsx}" --circular --no-warning --no-tree --exit-code circular:1 lint:fix: desc: "Auto-fix lint issues" @@ -345,8 +291,6 @@ tasks: desc: "Typecheck scripts" deps: [prepare] cmds: - - task: typecheck:_run - vars: { PROJECT: scripts/tsconfig.json } - task: typecheck:_run vars: { PROJECT: editor/scripts/tsconfig.json } @@ -362,14 +306,7 @@ tasks: deps: [install] cmds: - task: typecheck:_run - vars: { PROJECT: portal/tsconfig.json } - - typecheck:shared: - desc: "Typecheck the shared design system" - deps: [install] - cmds: - - task: typecheck:_run - vars: { PROJECT: shared/tsconfig.json } + vars: { PROJECT: editor/src/portal/tsconfig.json } typecheck:all: desc: "Typecheck all build variants" @@ -382,7 +319,6 @@ tasks: - task: typecheck:scripts - task: typecheck:prototypes - task: typecheck:portal - - task: typecheck:shared # ============================================================ # Quality Gate @@ -411,7 +347,6 @@ tasks: - task: lint - task: format:check - task: build - - task: build:portal - task: test - task: storybook:build @@ -423,7 +358,6 @@ tasks: desc: "Run tests" cmds: - task: test:editor - - task: test:portal test:editor: desc: "Run editor tests" @@ -431,12 +365,6 @@ tasks: cmds: - npx vitest run --root editor - test:portal: - desc: "Run portal tests" - deps: [prepare] - cmds: - - npx vitest run --root portal - test:watch: desc: "Run tests in watch mode" deps: [prepare] @@ -481,7 +409,7 @@ tasks: clean: desc: "Clean build artifacts and caches" cmds: - - cmd: powershell rm -Recurse -Force -ErrorAction SilentlyContinue node_modules/.vite, editor/dist, dist, dist-portal + - cmd: powershell rm -Recurse -Force -ErrorAction SilentlyContinue node_modules/.vite, editor/dist, dist platforms: [windows] - - cmd: rm -rf node_modules/.vite editor/dist dist dist-portal + - cmd: rm -rf node_modules/.vite editor/dist dist platforms: [linux, darwin] diff --git a/AGENTS.md b/AGENTS.md index 44647b0063..9afdad5937 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -139,7 +139,8 @@ The project structure is defined in `engine/pyproject.toml`. Any new dependencie #### Environment Variables - All `VITE_*` variables must be declared in the appropriate committed env file: - - `frontend/editor/.env` — core, proprietary, and shared vars + - `frontend/editor/.env` — core and shared vars (base, loaded in every mode) + - `frontend/editor/.env.proprietary` — proprietary-only vars, e.g. the admin portal's SaaS/account-link keys (layered on top of `.env` in proprietary mode) - `frontend/editor/.env.saas` — SaaS-only vars (layered on top of `.env` in SaaS mode) - `frontend/editor/.env.desktop` — desktop (Tauri)-only vars (layered on top of `.env` in desktop mode) - These files are committed to Git and must not contain private keys diff --git a/LICENSE b/LICENSE index 2b2f7fc085..971c9d0b16 100644 --- a/LICENSE +++ b/LICENSE @@ -20,8 +20,8 @@ if that directory exists, is licensed under the license defined in "frontend/edi if that directory exists, is licensed under the license defined in "frontend/editor/src/cloud/LICENSE". * All content that resides under the "frontend/editor/src/prototypes/" directory of this repository, if that directory exists, is licensed under the license defined in "frontend/editor/src/prototypes/LICENSE". -* All content that resides under the "frontend/portal/" directory of this repository, -if that directory exists, is licensed under the license defined in "frontend/portal/LICENSE". +* All content that resides under the "frontend/editor/src/portal/" directory of this repository, +if that directory exists, is licensed under the license defined in "frontend/editor/src/portal/LICENSE". * Content outside of the above mentioned directories or restrictions above is available under the MIT License as defined below. diff --git a/Taskfile.yml b/Taskfile.yml index 34c7c8bc28..26895723d0 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -79,86 +79,12 @@ tasks: OPEN: "true" dev:portal: - desc: "Start backend + developer portal concurrently on free ports" + desc: "Start backend + editor; the portal is an admin route at /portal" vars: PORTS: sh: '{{if eq OS "windows"}}{{.FIND_FREE_PORT_PS}} 8080 5173{{else}}{{.FIND_FREE_PORT_SH}} 8080 5173{{end}}' BACKEND_PORT: '{{index (splitList "\n" .PORTS) 0}}' - PORTAL_PORT: '{{index (splitList "\n" .PORTS) 1}}' - deps: - - task: backend:dev - vars: - PORT: '{{.BACKEND_PORT}}' - SECURITY_ENABLELOGIN: "true" - POLICIES_ENABLED: "true" - - task: frontend:dev:portal - vars: - PORT: '{{.PORTAL_PORT}}' - BACKEND_URL: 'http://localhost:{{.BACKEND_PORT}}' - MOCKS: 'false' - OPEN: "true" - - dev:portal:all: - desc: "Start backend + developer portal + editor concurrently on free ports" - vars: - PORTS: - sh: '{{if eq OS "windows"}}{{.FIND_FREE_PORT_PS}} 8080 5173 5174{{else}}{{.FIND_FREE_PORT_SH}} 8080 5173 5174{{end}}' - BACKEND_PORT: '{{index (splitList "\n" .PORTS) 0}}' - PORTAL_PORT: '{{index (splitList "\n" .PORTS) 1}}' - EDITOR_PORT: '{{index (splitList "\n" .PORTS) 2}}' - deps: - - task: backend:dev - vars: - PORT: '{{.BACKEND_PORT}}' - SECURITY_ENABLELOGIN: "true" - POLICIES_ENABLED: "true" - - task: frontend:dev:portal - vars: - PORT: '{{.PORTAL_PORT}}' - BACKEND_URL: 'http://localhost:{{.BACKEND_PORT}}' - # Point the portal's "Editor" app switcher at the editor we spawn here. - EDITOR_URL: 'http://localhost:{{.EDITOR_PORT}}/' - MOCKS: 'false' - OPEN: "true" - - task: frontend:dev - vars: - PORT: '{{.EDITOR_PORT}}' - BACKEND_URL: 'http://localhost:{{.BACKEND_PORT}}' - - dev:portal:all:saas: - desc: "Start SaaS backend + developer portal + editor concurrently on free ports" - vars: - PORTS: - sh: '{{if eq OS "windows"}}{{.FIND_FREE_PORT_PS}} 8080 5173 5174{{else}}{{.FIND_FREE_PORT_SH}} 8080 5173 5174{{end}}' - BACKEND_PORT: '{{index (splitList "\n" .PORTS) 0}}' - PORTAL_PORT: '{{index (splitList "\n" .PORTS) 1}}' - EDITOR_PORT: '{{index (splitList "\n" .PORTS) 2}}' - deps: - - task: backend:dev:saas - vars: - PORT: '{{.BACKEND_PORT}}' - POLICIES_ENABLED: "true" - - task: frontend:dev:portal - vars: - PORT: '{{.PORTAL_PORT}}' - BACKEND_URL: 'http://localhost:{{.BACKEND_PORT}}' - EDITOR_URL: 'http://localhost:{{.EDITOR_PORT}}/' - MOCKS: 'false' - OPEN: "true" - - task: frontend:dev - vars: - PORT: '{{.EDITOR_PORT}}' - BACKEND_URL: 'http://localhost:{{.BACKEND_PORT}}' - - dev:portal:proxy: - desc: "Editor + portal on ONE origin + backend via live dev servers (shared-token login)" - vars: - PORTS: - sh: '{{if eq OS "windows"}}{{.FIND_FREE_PORT_PS}} 8080 3000 5173 5174{{else}}{{.FIND_FREE_PORT_SH}} 8080 3000 5173 5174{{end}}' - BACKEND_PORT: '{{index (splitList "\n" .PORTS) 0}}' - PROXY_PORT: '{{index (splitList "\n" .PORTS) 1}}' - EDITOR_PORT: '{{index (splitList "\n" .PORTS) 2}}' - PORTAL_PORT: '{{index (splitList "\n" .PORTS) 3}}' + EDITOR_PORT: '{{index (splitList "\n" .PORTS) 1}}' deps: - task: backend:dev vars: @@ -169,18 +95,7 @@ tasks: vars: PORT: '{{.EDITOR_PORT}}' BACKEND_URL: 'http://localhost:{{.BACKEND_PORT}}' - - task: frontend:dev:portal - vars: - PORT: '{{.PORTAL_PORT}}' - BACKEND_URL: 'http://localhost:{{.BACKEND_PORT}}' - SUBPATH: portal - MOCKS: 'false' - - task: frontend:dev:portal:proxy:serve - vars: - PORT: '{{.PROXY_PORT}}' - BACKEND_URL: 'http://localhost:{{.BACKEND_PORT}}' - EDITOR_DEV_URL: 'http://localhost:{{.EDITOR_PORT}}' - PORTAL_DEV_URL: 'http://localhost:{{.PORTAL_PORT}}' + OPEN: "true" dev:saas: desc: "Start SaaS backend + frontend concurrently on free ports" @@ -228,24 +143,6 @@ tasks: - task: backend:build - task: frontend:build - preview:portal:proxy: - desc: "Build + serve editor + portal on ONE origin + backend (prod-like auth test)" - vars: - PORTS: - sh: '{{if eq OS "windows"}}{{.FIND_FREE_PORT_PS}} 8080 3000{{else}}{{.FIND_FREE_PORT_SH}} 8080 3000{{end}}' - BACKEND_PORT: '{{index (splitList "\n" .PORTS) 0}}' - PROXY_PORT: '{{index (splitList "\n" .PORTS) 1}}' - deps: - - task: backend:dev - vars: - PORT: '{{.BACKEND_PORT}}' - SECURITY_ENABLELOGIN: "true" - POLICIES_ENABLED: "true" - - task: frontend:preview:portal:proxy - vars: - PORT: '{{.PROXY_PORT}}' - BACKEND_URL: 'http://localhost:{{.BACKEND_PORT}}' - # ============================================================ # Test # ============================================================ diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/access/model/ResourceType.java b/app/proprietary/src/main/java/stirling/software/proprietary/access/model/ResourceType.java index 9d0108db0f..4b32d6ae6a 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/access/model/ResourceType.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/access/model/ResourceType.java @@ -2,7 +2,8 @@ package stirling.software.proprietary.access.model; /** Types of resources whose access can be gated by {@link ResourceGrant}. */ public enum ResourceType { - // The admin portal / processor (frontend/portal). Singleton resource (empty resourceId). + // The admin portal / processor (frontend/editor/src/portal). Singleton resource (empty + // resourceId). PORTAL, // A stored S3/MCP/API integration configuration. INTEGRATION_CONFIG diff --git a/frontend/.gitignore b/frontend/.gitignore index 3a5de11c57..e07dce196a 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -11,7 +11,6 @@ # production /build /dist -/dist-portal /storybook-static /editor/build @@ -27,8 +26,8 @@ # Root .gitignore ignores all .env* - whitelist only our committed ones, anchored # to their app so a stray top-level frontend/.env stays ignored (Storybook's SaaS # mock env is injected via .storybook/main.ts, not a file). -!/portal/.env !/editor/.env +!/editor/.env.proprietary !/editor/.env.desktop !/editor/.env.saas diff --git a/frontend/.prettierignore b/frontend/.prettierignore index 63d7130f47..25caf7cd10 100644 --- a/frontend/.prettierignore +++ b/frontend/.prettierignore @@ -1,5 +1,4 @@ dist/ -dist-portal/ editor/dist/ # Tauri/Cargo build output (binary assets named *.js etc. confuse Prettier). # Match nested target/ dirs too - provisioner/ and thumbnail-handler/ each @@ -9,7 +8,7 @@ editor/src-tauri/gen/ node_modules/ editor/public/vendor/ # Auto-generated by MSW (`msw init`); regenerated verbatim, not hand-formatted. -portal/public/mockServiceWorker.js +editor/public/mockServiceWorker.js # Auto-generated OG/social-preview metadata (scripts/generate-og-metadata.mjs); regenerated verbatim. editor/public/og-metadata.json editor/src/core/data/ogImageMap.json diff --git a/frontend/.storybook/main.ts b/frontend/.storybook/main.ts index c016a5c37d..4d4aadc8f8 100644 --- a/frontend/.storybook/main.ts +++ b/frontend/.storybook/main.ts @@ -6,16 +6,13 @@ import tsconfigPaths from "vite-tsconfig-paths"; * Storybook 9 ships essentials, interactions, and docs as built-ins, so the * addon list is just the extras we want: theme switching + a11y auditing. * - * Story files live next to their components in shared/, portal/src/, and - * editor/src/ — the design system is shared by BOTH apps, so both surface - * their stories here. MDX docs pages live in portal/src/docs/. + * Story files live next to their components under editor/src/ (which includes + * the portal layer at editor/src/portal/). MDX docs pages live in + * editor/src/portal/docs/. */ const config: StorybookConfig = { stories: [ - "../portal/src/**/*.mdx", - "../portal/src/**/*.stories.@(ts|tsx)", - "../shared/**/*.mdx", - "../shared/**/*.stories.@(ts|tsx)", + "../editor/src/portal/**/*.mdx", "../editor/src/**/*.stories.@(ts|tsx)", ], addons: ["@storybook/addon-themes", "@storybook/addon-a11y"], @@ -26,17 +23,21 @@ const config: StorybookConfig = { typescript: { reactDocgen: "react-docgen-typescript", }, - // Serve the MSW worker file from portal/public so Storybook can intercept - // network calls the same way the dev portal does. - staticDirs: ["../portal/public"], + // Serve the MSW worker file from the portal's public dir so Storybook can + // intercept network calls the same way the dev portal does. + staticDirs: ["../editor/public"], viteFinal: async (config) => { - // Wire @portal/* and @shared/* aliases directly on the Storybook bundler so - // portal story imports resolve without needing the portal's vite config. + // Wire the @portal/* alias directly on the Storybook bundler so portal + // story imports resolve without needing the portal's vite config. config.resolve = config.resolve ?? {}; config.resolve.alias = { ...(config.resolve.alias ?? {}), - "@portal": resolve(__dirname, "../portal/src"), - "@shared": resolve(__dirname, "../shared"), + "@portal": resolve(__dirname, "../editor/src/portal"), + // Direct layer aliases so .storybook config files (preview.tsx), which sit + // outside src/ and so aren't covered by tsconfigPaths, can import layer + // modules (e.g. the auth supabase client that moved into proprietary). + "@proprietary": resolve(__dirname, "../editor/src/proprietary"), + "@core": resolve(__dirname, "../editor/src/core"), }; // Editor stories import via @app/* (proprietary→core fallback), @core/* and // @proprietary/*. Resolve them exactly the way the editor's own build does — diff --git a/frontend/.storybook/preview.tsx b/frontend/.storybook/preview.tsx index 1f9c935fe6..d086613f44 100644 --- a/frontend/.storybook/preview.tsx +++ b/frontend/.storybook/preview.tsx @@ -19,11 +19,11 @@ import { ThemeProvider } from "@portal/contexts/ThemeContext"; import { UIProvider } from "@portal/contexts/UIContext"; import { mantineTheme } from "@portal/theme/mantineTheme"; import { handlers } from "@portal/mocks/handlers"; -import { configureSupabase } from "@shared/auth/supabase/supabaseClient"; +import { configureSupabase } from "@proprietary/auth/supabase/supabaseClient"; import "@mantine/core/styles.css"; -import "@shared/tokens/tokens.css"; -import "@shared/tokens/base.css"; +import "@core/tokens/tokens.css"; +import "@core/tokens/base.css"; // Start MSW once. Storybook runs in a browser so this uses the service worker. initialize({ onUnhandledRequest: "bypass" }, handlers); diff --git a/frontend/editor/.env.proprietary b/frontend/editor/.env.proprietary new file mode 100644 index 0000000000..ed5498ba3a --- /dev/null +++ b/frontend/editor/.env.proprietary @@ -0,0 +1,32 @@ +############################################################################### +# Proprietary-build environment variables, layered on top of `.env` when Vite +# runs in `--mode proprietary` (so the core/OSS build never sees them). These +# back the admin portal mounted at /portal. Committed to Git, so no private keys; +# machine-specific overrides go in the uncommitted .env.proprietary.local / +# .env.local. (VITE_STRIPE_PUBLISHABLE_KEY lives in the base `.env` as it is +# shared across builds.) +############################################################################### + +# Where the portal's "Editor" switcher / non-admin redirect points. "/" is +# correct: the editor serves the portal at /portal on the same origin. In dev, +# override in .env.local with your running editor's URL (e.g. +# VITE_EDITOR_URL=http://localhost:5173/). +VITE_EDITOR_URL=/ + +# Force the portal's MSW mocks on ("true") or off ("false"). Empty = default +# (on in dev, off in production builds). Set "false" to run against the real +# backend. +VITE_PORTAL_MOCKS= + +# Hosted SaaS Supabase project for the self-hosted portal's IN-APP account +# linking (both values are public). Set per deploy; absent -> the account-link +# UI shows a "configure" state. For local e2e, point these at the SaaS Supabase +# project the local backend links against. +VITE_SAAS_SUPABASE_URL= +VITE_SAAS_SUPABASE_ANON_KEY= + +# Hosted SaaS Java backend base URL (e.g. https://api.stirlingpdf.com). Used for +# ATTENDED portal -> SaaS reads (wallet, billing, plans, checkout) with the +# admin's Supabase JWT. Distinct from the local backend (reached same-origin via +# the editor's vite proxy). Absent -> wallet/billing surfaces stay on the mock. +VITE_SAAS_API_URL= diff --git a/frontend/editor/public/locales/en-US/translation.toml b/frontend/editor/public/locales/en-US/translation.toml index 7f81e56f3b..d07d8379da 100644 --- a/frontend/editor/public/locales/en-US/translation.toml +++ b/frontend/editor/public/locales/en-US/translation.toml @@ -6048,6 +6048,1857 @@ stepOf = "Step {{step}} of {{total}}" toolChainDesc = "Configure the tools this policy runs on each document." typesSelected = "{{count}} types selected" +[portal.accountLink.card] +billingNote = "Unattended processing bills against your org wallet." +eyebrow = "Account link" +linkButton = "Link your Stirling account" +linked = "Linked" +linkedAs = "Linked as {{name}}." +linkedGeneric = "This instance is linked." +notLinked = "Not linked" +title = "Link this org to its Stirling account" +unlink = "Unlink" + +[portal.accountLink.card.error] +title = "Couldn't link" + +[portal.accountLink.card.loginNotConfigured] +after = "to enable account linking against the hosted Stirling account. In dev you can simulate sign-in from the link dialog." +before = "Set" +title = "SaaS login not configured" + +[portal.accountLink.gate] +action = "Link account" +description = "Link this org's Stirling account to use billable features." +title = "Link to unlock" +titleFeature = "Link to unlock {{feature}}" + +[portal.accountLink.instances] +active = "Active" +revoke = "Revoke" +revoked = "Revoked" +unnamed = "Unnamed instance" + +[portal.accountLink.instances.columns] +instance = "Instance" +lastSeen = "Last seen" +linked = "Linked" +status = "Status" + +[portal.accountLink.instances.empty] +description = "Link this org's account, then register your self-hosted instances to see them here." +title = "No linked instances" + +[portal.accountLink.instances.time] +daysAgo_one = "{{count}}d ago" +daysAgo_other = "{{count}}d ago" +hoursAgo_one = "{{count}}h ago" +hoursAgo_other = "{{count}}h ago" +justNow = "just now" +minutesAgo_one = "{{count}}m ago" +minutesAgo_other = "{{count}}m ago" +never = "never" + +[portal.accountLink.modal] +linkSubtitle = "Sign in to the account this server should bill against." +linkTitle = "Link your Stirling account" +reauthSubtitle = "Your session expired — sign back in to your Stirling account. Your instance stays linked." +reauthTitle = "Sign in again" +simulateSignIn = "Simulate sign-in (dev)" + +[portal.accountLink.modal.loginNotConfigured] +after = "to enable in-app linking against the hosted Stirling account." +and = "and" +before = "Set" +title = "SaaS login not configured" + +[portal.accountLink.panel] +instancesSub = "Every self-hosted instance registered to this org. Revoke a credential to immediately cut off its unattended access." +instancesTitle = "Linked instances" +revokeError = "Couldn't revoke instance" +sub = "Link this self-hosted org to its Stirling account so unattended processing bills against your org wallet." + +[portal.accountLink.panel.loadError] +forbidden = "Only the team owner can view the org's linked instances." +generic = "Couldn't load the team's linked instances. Try again in a moment." +title = "Couldn't load linked instances" + +[portal.accountLink.state] +free = "Editor plan" +subscribed = "Processor plan" +unlinked = "Not linked" + +[portal.agentBuilder] +bootstrapFromDocument = "Bootstrap from document" +sectionsAriaLabel = "Agent builder sections" +selectorAriaLabel = "Agents" +subtitle = "Design, test and ship the AI agents that classify, extract from and route your documents. Define scenarios, fence tool access, run a golden set, and publish a version." +title = "Agent Builder" + +[portal.agentBuilder.bootstrap] +cancel = "Cancel" +dropzoneText = "Choose a sample document (PDF or image)" +lead = "Drop a sample document and we'll propose scenarios and an extraction schema you can refine. Nothing is published until you review it." +submit = "Bootstrap agent" +subtitle = "Seed a new agent from one representative file" +title = "Bootstrap from a document" + +[portal.agentBuilder.empty] +description = "Bootstrap an agent from a sample document to seed its scenarios and extraction schema, then refine and publish." +title = "No agents yet" + +[portal.agentBuilder.evals] +casesPassing = "Cases passing" +columnCase = "Eval case" +columnLatency = "Latency" +columnResult = "Result" +fail = "fail" +goldenSetPassRate = "Golden-set pass rate" +latencyMs = "{{ms}} ms" +notRun = "not run" +pass = "pass" +passRate = "Pass rate" +runEvals = "Run evals" + +[portal.agentBuilder.evals.empty] +description = "Evals turn your scenarios into a repeatable golden set. Upgrade to capture pass-rate over time and gate publishes on it." +title = "No golden set yet" + +[portal.agentBuilder.kpi] +acrossGoldenSets = "across golden sets" +activeAgents = "Active agents" +avgPassRate = "Avg eval pass rate" +fleetWide = "fleet-wide" +latestPublished = "Latest published" +scenarios = "Scenarios" +testCases = "test cases" +totalDescription_one = "{{count}} total" +totalDescription_other = "{{count}} total" + +[portal.agentBuilder.scenarios] +add = "Add" +addScenario = "Add scenario" +enable = "Enable" +expectationLabel = "Expected behaviour" +expectationPlaceholder = "What the agent should do" +inEval = "in eval" +mute = "Mute" +muted = "muted" +nameLabel = "Name" +namePlaceholder = "e.g. Compliance escalation" + +[portal.agentBuilder.tabs] +evals = "Evals" +scenarios = "Scenarios" +tools = "Tools" +versions = "Versions" + +[portal.agentBuilder.tools] +broadAccess = "Broad access" +deniedHint = "Selected tools are blocked. Everything else stays callable." +deniedTools = "Denied tools" +governanceGate = "Tool governance is available on the Enterprise plan." +restricted = "Restricted" +restrictedAccess = "Restricted tool access" +restrictedDescription = "Allow every tool except the ones you deny below." + +[portal.agentBuilder.versions] +current = "current" +historyGate = "Full version history and rollback are available on the Enterprise plan." +publish = "Publish" +rollBack = "Roll back" + +[portal.assistant] +close = "Close assistant" +error = "Couldn't reach the assistant." +errorWithDetail = "Couldn't reach the assistant: {{detail}}" +inputAriaLabel = "Ask the assistant" +inputPlaceholder = "Ask about Stirling…" +open = "Open assistant" +send = "Send" +title = "Assistant" +tryAsking = "Try asking" +typing = "Typing" + +[portal.auth] +loading = "Loading" +redirectingToEditor = "Redirecting to the editor..." + +[portal.billing.checkout] +noClientSecret = "Edge function returned no client_secret." +subtitle = "Add a card to keep going past your free Editor-plan grant. Stripe handles the rest." +title = "Turn on the Processor plan" + +[portal.billing.checkout.error] +title = "Couldn't start checkout" + +[portal.billing.checkout.notConfigured] +bodyAfter = "in the portal env to enable in-app checkout." +bodyBefore = "Set" +title = "Stripe not configured" + +[portal.billing.enterpriseUpsell] +cta = "Build your Enterprise quote" +description = "Committed volume discounts, air-gapped deployment, custom MSA and security reviews, and 3rd-party distributor partnerships." +eyebrow = "Volume discount · 1M+ PDFs" +title = "Stirling Enterprise" + +[portal.billing.freeEditors] +activeThisMonth = "Active this month" +cost = "Cost" +editorsDeployed = "Editors deployed" +inviteTeammates = "Invite teammates" +pdfsEdited = "PDFs edited" +previewBadge = "Preview · sample data" +subtitle = "Deploy anywhere, for your whole team." +title = "Free PDF Editors" + +[portal.billing.freePlan] +checkoutErrorTitle = "Couldn't start checkout" +currentPlan = "Current plan" +freeForever = "Free forever" +noTeamResolved = "No team is resolved on your wallet yet — refresh and try again." +ownerOnly = "Only the team owner can switch on the Processor plan." +planName = "Editor" +ssoIncluded = "SSO included" +switchOnProcessor = "Switch on the Processor →" +unlimitedUsers = "Unlimited users" + +[portal.billing.invoices] +columnAmount = "Amount" +columnDate = "Date" +columnDescription = "Description" +columnPdfsProcessed = "PDFs processed" +columnStatus = "Status" +descriptionFallback = "Invoice" +downloadAriaLabel = "Download invoice {{number}} as PDF" +emptyDescription = "Once your team subscribes and the first cycle closes, your invoices appear here." +emptyTitle = "No invoices yet" +fetchLimitNote_one = "Showing your {{count}} most recent invoices. Older invoices are in the Stripe portal." +fetchLimitNote_other = "Showing your {{count}} most recent invoices. Older invoices are in the Stripe portal." +loadError = "Couldn't load invoices: {{error}}" +pdfLink = "PDF ↓" +showAll_one = "Show all {{count}}" +showAll_other = "Show all {{count}}" +showFewer_one = "Show fewer (top {{count}})" +showFewer_other = "Show fewer (top {{count}})" +showMostRecent_one = "Show {{count}} most recent" +showMostRecent_other = "Show {{count}} most recent" +title = "Invoice history" +viewAriaLabel = "View invoice {{number}} in Stripe" +viewLink = "View ↗" + +[portal.billing.linkPrompt] +cta = "Link Stirling account" +description = "Manual PDF editing — view, sign, merge, split, watermark, compress, convert, manual OCR — is always free, linked or not. Link to claim 500 free PDFs of metered processing (automation, AI, and the API); when you need more, turn on the Processor plan and only pay for what you use." +title = "Link your Stirling account" + +[portal.billing.paymentMethod] +billedMonthly = "Billed monthly" +cardEnding = "{{brand}} ending {{last4}}" +cardFallback = "Card" +expiresBilledMonthly = "Expires {{expiry}} · billed monthly" +eyebrow = "Payment method" +managedSub = "Your card and billing details are kept securely in Stripe's customer portal." +managedTitle = "Managed in Stripe" +update = "Update" + +[portal.billing.pdfsProcessed] +emptyPeriod = "No metered processing yet this period." +eyebrow = "PDFs processed this period" +legendValue_one = "{{formatted}} PDFs" +legendValue_other = "{{formatted}} PDFs" +segbarAriaLabel = "Metered PDFs split by category" +segmentAgentsDesc = "AI agent actions" +segmentAgentsLabel = "Agents" +segmentApiDesc = "Direct API requests" +segmentApiLabel = "API" +segmentAutomationDesc = "Automations & pipelines" +segmentAutomationLabel = "Automation" +unit = "metered PDFs" + +[portal.billing.spendLimit] +adjustLimit = "Adjust limit" +cancel = "Cancel" +capControlNote = "Changes apply immediately — raise or lower the ceiling any time." +capSuffix = "/ month" +capSuffixWithDocs = "/ month · ≈ {{documents}} documents" +displaySub = "You're only billed for what you process automatically — never past the ceiling." +editTitle = "Set your monthly ceiling" +eyebrow = "Spend limit" +guardrailBody = "a hard ceiling — you're never billed past it. At the cap, metered processing pauses (unlimited PDF editing keeps working) until you raise it or the cycle resets. Nothing is lost." +guardrailLabel = "Your guardrail:" +noCap = "no cap" +pctUsed = "{{pct}}% used" +remaining = "{{amount}} remaining" +save = "Save limit" +saveError = "Couldn't save limit" +thisPeriodUncapped = "{{amount}} this period · uncapped" +usedThisMonth = "{{amount}} used this month" +useSuggested = "Use suggested · {{amount}} / month" + +[portal.billing.spendLimit.projection] +body_one = "At {{rate}}/day you reach the cap in ~{{count}} day (~{{monthEnd}} month-end). Suggested limit ~{{suggested}}." +body_other = "At {{rate}}/day you reach the cap in ~{{count}} days (~{{monthEnd}} month-end). Suggested limit ~{{suggested}}." +label = "Projected to exceed." + +[portal.billing.spendThisMonth] +eyebrow = "Spend this month" +processed_one = "{{formattedCount}} PDF processed." +processed_other = "{{formattedCount}} PDFs processed." +processedWithRate_one = "{{formattedCount}} PDF processed, at {{rate}} each." +processedWithRate_other = "{{formattedCount}} PDFs processed, at {{rate}} each." + +[portal.billing.subscribedPlan.capWarn] +approachingBody = "Raise it now so automated processing never pauses." +approachingTitle = "You're at {{pct}}% of your monthly spend limit" +raiseLimit = "Raise limit" +reachedBody = "Metered processing is paused until you raise the limit or the cycle resets. Unlimited PDF editing keeps working." +reachedTitle = "Monthly spend limit reached" + +[portal.billing.subscribedPlan.portalError] +title = "Couldn't open Stripe portal" + +[portal.billing.walletMeter] +capSuffix_one = "of {{allowance}} free PDFs used" +capSuffix_other = "of {{allowance}} free PDFs used" +eyebrow = "Processor trial" +statusLabel_one = "{{remaining}} left" +statusLabel_other = "{{remaining}} left" +sub = "Use the PDF Editor for free. Pay to process PDFs automatically." +title_one = "Process {{allowance}} PDFs free" +title_other = "Process {{allowance}} PDFs free" +titleWithRate_one = "Process {{allowance}} PDFs free, then {{rate}}/PDF" +titleWithRate_other = "Process {{allowance}} PDFs free, then {{rate}}/PDF" + +[portal.catalogue.card] +lockedAriaLabel = "Locked" +openAriaLabel = "Open {{name}} component" + +[portal.catalogue.detail] +addToProject = "Add to project" +ariaLabel = "Component detail" +tabsAriaLabel = "Component detail sections" +upgradeToUnlock = "Upgrade to unlock" + +[portal.catalogue.detail.code] +install = "Install" +usage = "Usage" + +[portal.catalogue.detail.locked] +description = "{{name}} is included from the {{tier}} plan. Upgrade to embed it." +title = "Not available on your plan" + +[portal.catalogue.detail.preview] +badge = "Live preview" +note = "Interactive sandbox renders here" + +[portal.catalogue.detail.pricing] +note = "Metered per {{unit}}. Usage beyond the monthly free quota is billed to your account and itemised under Usage & Billing." + +[portal.catalogue.detail.stats] +billedOn = "Billed on" +embeds30d = "Embeds (30d)" +freeQuota = "Free quota" +freeQuotaValue = "{{amount}} / mo" +maturity = "Maturity" +none = "None" +perAction = "Per action" +price = "Price" + +[portal.catalogue.detail.tabs] +code = "Code" +overview = "Overview" +pricing = "Pricing" +props = "Props / API" + +[portal.catalogue.props] +optional = "optional" +required = "required" + +[portal.catalogue.props.columns] +description = "Description" +name = "Prop" +required = "Required" +type = "Type" + +[portal.catalogue.summary] +componentsGa = "Components GA" +componentSpendMtd = "Component spend (MTD)" +embedsThisMonth = "Embeds this month" +inBeta = "In beta" + +[portal.common] +inviteMember = "Invite member" + +[portal.componentsView] +subtitle = "Embeddable SDK widgets you drop into your own app — a viewer, an e-sign flow, an AI review panel. Each is metered per action. Click a card for install, usage and props." +title = "Components" + +[portal.componentsView.empty] +description = "The component catalogue could not be loaded. Try again shortly." +title = "No components available" + +[portal.componentsView.lockedBanner] +description = "GA components are available on Pay-as-you-go; a few Beta components are enterprise-only. Locked cards show an upgrade nudge." +title = "Some components need a paid plan" + +[portal.docs.authentication] +codeCaption = "every request" +eyebrow = "GETTING STARTED" +lead = "All requests authenticate with a bearer token. Keys are scoped per environment and never expire unless rotated." +liveKey = "Production keys — billed, rate-limited per your plan." +testKey = "Sandbox keys — free, return synthetic fixtures." +title = "Authentication" + +[portal.docs.components] +codeCaption = "embed the viewer" +eyebrow = "COMPONENTS" +lead = "Embeddable UI for review queues and document inspection. Bring your own styles or use the shipped theme." +title = "Drop-in viewers" + +[portal.docs.endpoints] +eyebrow = "API REFERENCE" +fieldCount_one = "{{count}} field" +fieldCount_other = "{{count}} fields" +filterAll = "All" +filterAriaLabel = "Filter endpoints by vertical" +lead = "Every document type is a typed endpoint. POST a file, receive schema-validated JSON. Filter by vertical below." +title = "Endpoints" + +[portal.docs.errors] +codeCaption = "422 Unprocessable Entity" +eyebrow = "API REFERENCE" +lead = "Errors return a stable machine-readable code plus a human message. The 4xx body always includes a request_id for support." +title = "Errors" + +[portal.docs.nav] +ariaLabel = "Documentation" + +[portal.docs.nav.empty] +description = "The documentation index could not be loaded." +title = "Docs unavailable" + +[portal.docs.quickstart] +eyebrow = "GETTING STARTED" +lead = "Send your first document to a typed endpoint and get structured JSON back in three steps. No model training, no prompt engineering." +title = "Quickstart" + +[portal.docs.quickstart.callout] +bodyAfterLink = "for copy-paste recipes." +bodyBeforeLink = "wire the same call into a pipeline to chain validation, redaction, and delivery — or expose it to an agent over MCP. See" +label = "Next:" +link = "Playbooks" + +[portal.docs.quickstart.step1] +body = "Create a scoped key from the Infrastructure tab. Keys carry rate limits and an optional IP allowlist. Export it into your shell:" +title = "Issue an API key" + +[portal.docs.quickstart.step2] +body = "POST a file to any typed endpoint. The endpoint determines the schema you get back — here, the invoice extractor." +snippetCaption = "extract an invoice" +title = "Send a document" + +[portal.docs.quickstart.step3] +body = "Every response is validated against the endpoint schema, with a confidence score and per-field provenance." +codeCaption = "200 OK" +title = "Read the structured result" + +[portal.docs.rateLimits] +burst = "Burst" +codeCaption = "429 Too Many Requests" +concurrency = "Concurrency" +eyebrow = "GETTING STARTED" +lead = "Limits scale with your plan. A 429 response includes a Retry-After header; the SDKs back off automatically." +requestsPerMinute = "Requests / minute" +title = "Rate limits & quotas" + +[portal.docs.recipes] +cloneButton = "Clone recipe" +eyebrow = "PLAYBOOKS" +lead = "End-to-end patterns that chain sources, operations, and destinations. Each maps to a pipeline you can clone." +title = "Recipes" + +[portal.docs.sdks] +eyebrow = "SDKS" +lead = "First-party clients with typed responses, automatic retries, and streaming uploads. All track the same endpoint catalogue." +title = "Official SDKs" + +[portal.docs.sdks.status] +beta = "Beta" +deprecated = "Deprecated" + +[portal.docs.skills] +eyebrow = "SKILLS" +lead = "Bundled, named capabilities your agent invokes as a single tool. Each skill is a deterministic op chain with evals attached." +title = "Agent skills" + +[portal.docs.webhooks] +codeCaption = "document.processed" +eyebrow = "API REFERENCE" +lead = "Subscribe to document.processed, pipeline.completed, and quota.threshold events. Payloads are signed with HMAC-SHA256." +title = "Webhooks" + +[portal.docs.webhooks.callout] +afterHelper = "helper." +beforeHelper = "header against your signing secret before trusting a payload. SDKs ship a" +beforeSignature = "Verify the" + +[portal.documents] +subtitle = "Review and approve documents moving through your pipelines." +title = "Documents" + +[portal.documents.audit] +empty = "No events recorded yet." + +[portal.documents.drawer] +sectionsAriaLabel = "Document detail sections" + +[portal.documents.drawer.tabs] +audit = "Audit" +extractions = "Extractions" +overview = "Overview" + +[portal.documents.elevation] +requestAccess = "Request access" + +[portal.documents.elevation.active] +description = "Temporary grant — access is logged and time-boxed." +descriptionFourEyes = "Temporary grant — a peer reviewer was notified (four-eyes)." +title = "Access expires in {{time}}" + +[portal.documents.elevation.gated] +description = "Content is gated by zero-standing-access. Requesting starts a time-boxed grant." +descriptionFourEyes = "Content is gated by zero-standing-access. Requesting starts a time-boxed grant and notifies a peer reviewer (four-eyes)." +title = "Sensitive document" + +[portal.documents.extractions] +empty = "No fields were extracted from this document." +masked = "Extracted fields are hidden. Request timed access to view this document's content." + +[portal.documents.extractions.columns] +confidence = "Confidence" +field = "Field" +value = "Value" + +[portal.documents.filters] +all = "All" +archived = "Archived" +ariaLabel = "Filter documents by status" +needsReview = "Needs review" +processed = "Processed" + +[portal.documents.overview] +confidence = "Confidence" +fieldsExtracted = "Fields extracted" +received = "Received" +source = "Source" +status = "Status" +type = "Type" + +[portal.documents.queue.empty] +description = "As sources feed documents into your pipelines they'll appear here for review." +title = "No documents in the queue" + +[portal.documents.summary] +avgConfidence = "Avg confidence" +inQueue = "In queue" +needsReview = "Needs review" +processedToday = "Processed today" + +[portal.documents.table] +empty = "No documents match this filter." +sensitiveLabel = "Sensitive" +sensitiveTitle = "Sensitive — access required" + +[portal.documents.table.columns] +confidence = "Confidence" +fields = "Fields" +name = "Name" +source = "Source" +status = "Status" +time = "Time" +type = "Type" + +[portal.editorAdmin] +subtitle = "Deploy the Stirling PDF Editor, pair self-hosted instances to your org, and operate the running fleet — targets, health, credentials, and offline activation in one place." +title = "Editor deployment" + +[portal.editorAdmin.health.columns] +activeUsers = "Active users" +host = "Host" +lastSeen = "Last seen" +region = "Region" +status = "Status" +version = "Version" + +[portal.editorAdmin.health.empty] +description = "Deploy a target and pair it to see live instance health here." +title = "No instances reporting" + +[portal.editorAdmin.offlineActivation] +enterpriseTag = "Enterprise" +generateButton = "Generate offline bundle" +lockCopy = "Offline and on-prem activation is part of Enterprise." +subtitle = "Generate a signed activation bundle for an offline or on-prem install with no outbound network path. Transfer it to the instance and apply it during first-run setup." +talkToSales = "Talk to sales" +title = "Air-gapped activation" + +[portal.editorAdmin.offlineActivation.readyBanner] +description = "{{file}} is signed and ready to transfer. It activates one instance and expires in 14 days." +title = "Bundle ready" + +[portal.editorAdmin.pairing] +generated = "Generated ✓" +generateNewCode = "Generate new code" +lockCopy = "IaC provisioning is part of Enterprise." +rotate = "Rotate" +talkToSales = "Talk to sales" + +[portal.editorAdmin.sections.health] +sub = "Every Editor instance reporting in — version, region, status, last seen, and active users." +title = "Instance health" + +[portal.editorAdmin.sections.pairing] +sub = "Connect a self-hosted editor to this org. Generate a token, hand off a short code, or wire it through IaC." +title = "Pairing" + +[portal.editorAdmin.sections.targets] +sub = "Where the Editor runs. Copy a snippet to stand up a self-hosted instance, or use Managed Cloud with no ops." +title = "Deployment targets" + +[portal.editorAdmin.serviceToken] +currentToken = "Current token" +lastRotated = "Last rotated" +rotateButton = "Rotate service token" +subtitle = "Instances authenticate to the org with this credential. Rotate it on a schedule or immediately after a suspected leak." +title = "Service token" + +[portal.editorAdmin.serviceToken.rotatedBanner] +description = "A new token was issued. Update each self-hosted instance's STIRLING_SERVICE_TOKEN within the 24h grace window or they'll drop offline." +title = "Rotate running instances" + +[portal.editorAdmin.targets] +instanceCount_one = "{{count}} instance" +instanceCount_other = "{{count}} instances" +talkToSales = "Talk to sales" +upgradePlan = "Upgrade plan" + +[portal.editorAdmin.targets.lock] +enterprise = "On-prem and Kubernetes self-hosting are part of Enterprise." +paid = "Self-hosting with Docker and Kubernetes unlocks on a paid plan." + +[portal.editorAdmin.targets.state] +available = "Available" +locked = "Locked" +running = "Running" + +[portal.errorBoundary] +description = "This view hit an unexpected error. Try again, or pick another section from the sidebar." +retry = "Try again" +title = "Something went wrong on this page" + +[portal.forkWizard] +subtitle = "Clone a proven workflow and tune it — every template ships the same four-stage backbone." +title = "Fork a starter pipeline" + +[portal.forkWizard.action] +cancel = "Cancel" +deploy = "Deploy pipeline" +pickAnother = "Pick another" + +[portal.forkWizard.status] +building = "Building…" +ready = "Ready to deploy" + +[portal.home.chart.empty] +description = "Once documents are processed, your 30-day usage appears here." +title = "No usage yet" + +[portal.home.kpis.enterprise] +docs30d = "Docs / 30d" +evalPassRate = "Eval pass rate" +p95Latency = "P95 latency" +slaUptime = "SLA uptime (30d)" + +[portal.home.kpis.free] +agents = "Agents" +docsProcessed = "Docs processed" +docsProcessedDescription = "Free plan cap" +operations = "Operations" +pipelines = "Pipelines" + +[portal.home.kpis.pro] +agentsActive = "Agents active" +docs30d = "Docs / 30d" +evalPassRate = "Eval pass rate" +pipelines = "Pipelines" + +[portal.home.onboarding] +progress = "{{done}} / {{total}} done" +runAgain = "Run again" +start = "Start" +subtitle = "Four steps to a production-shaped Stirling project." +title = "Get to value" + +[portal.home.onboarding.empty] +description = "Onboarding tasks will appear here once your workspace is set up." +title = "No onboarding steps yet" + +[portal.home.productGrid] +ariaLabel = "Process PDFs at scale" + +[portal.home.productGrid.agents] +blurb = "Wire your agent via MCP, REST, or tool definitions. Deterministic operations, scenarios, evals." +cta = "Connect an agent" +title = "Agents" + +[portal.home.productGrid.pipelines] +badge = "Hero" +blurb = "Compose document workflows from typed operations. Upload a sample to get suggestions or start blank." +cta = "Build a pipeline" +title = "Pipelines" + +[portal.home.productGrid.sources] +blurb = "Attach pipelines where PDFs already live — S3, agents, SharePoint, webhooks, batch, email." +cta = "Connect a source" +title = "Sources" + +[portal.home.quickActions] +subtitle = "Top tasks for today" +title = "Quick actions" + +[portal.home.quickActions.buildPipeline] +blurb = "3-step composer over the typed op library" +title = "Build a pipeline" + +[portal.home.quickActions.connectSource] +blurb = "S3, agents, webhooks, watched folders" +title = "Connect a source" + +[portal.home.quickActions.issueApiKey] +blurb = "Scoped key with rate limits and IP allowlist" +title = "Issue an API key" + +[portal.home.quickActions.tryOp] +blurb = "Drop a sample, pick an op, see the JSON" +title = "Try a PDF operation" + +[portal.home.regions] +subtitle = "Real-time status for every deployed Stirling region." +title = "Region health" + +[portal.home.regions.empty] +description = "Once a region is deployed, its health appears here." +title = "No regions yet" + +[portal.infrastructure] +manageEditorDeployment = "Manage Editor deployment" +sectionsAriaLabel = "Infrastructure sections" +subtitle = "Deployments, credentials, security posture, storage, and the audit trail for your Stirling workspace." +title = "Infrastructure" + +[portal.infrastructure.apiKeys] +createKey = "Create key" +heading = "API keys" +subheading = "Scoped credentials with per-key rate limits, permissions, and IP allowlists." + +[portal.infrastructure.apiKeys.card] +allowedIps = "Allowed IPs" +anyIp = "Any IP (no allowlist)" +created = "Created" +lastUsed = "Last used" +permissions = "Permissions" +rateLimit = "Rate limit" +rateLimitValue = "{{value}} req/min" +usageMonth = "Usage this month" +usageToday = "Usage today" + +[portal.infrastructure.apiKeys.empty] +description = "Create a scoped key to start calling the Stirling API." +title = "No API keys yet" + +[portal.infrastructure.audit] +filterAriaLabel = "Filter audit events by category" +heading = "Audit logs" +latencyValue = "{{value}} ms" +noEventsInCategory = "No events in this category." +subheading = "Every authentication, configuration, and processing event across your workspace." + +[portal.infrastructure.audit.columns] +actor = "Actor" +event = "Event" +latency = "Latency" +status = "Status" +target = "Target" +timestamp = "Timestamp" + +[portal.infrastructure.audit.empty] +description = "Workspace activity will appear here as it happens." +title = "No audit events" + +[portal.infrastructure.audit.filters] +all = "All" +auth = "Auth" +config = "Config" +elevation = "Elevation" +processing = "Processing" +security = "Security" + +[portal.infrastructure.audit.metrics] +config = "Config" +elevation = "Elevation" +processing = "Processing" +totalEvents = "Total events · 24h" + +[portal.infrastructure.createKey] +cancel = "Cancel" +createKey = "Create key" +done = "Done" +ipAllowlistHelper = "Comma-separated CIDR ranges. Leave blank to allow any IP." +ipAllowlistLabel = "IP allowlist" +keyNameLabel = "Key name" +keyNamePlaceholder = "e.g. Production · ingest" +permissionsLabel = "Permissions" +secretKeyCaption = "Secret key" +secretWarning = "Store this in a secrets manager. Stirling only ever stores a hash — there is no way to recover it later." +subtitle = "Scope the key to the minimum it needs. You can rotate or revoke at any time." +subtitleCreated = "Copy this secret now — it won't be shown again." +title = "Create API key" +titleCreated = "Key created" + +[portal.infrastructure.deployments] +msValue = "{{value}} ms" +throughputValue = "{{value}}/min" + +[portal.infrastructure.deployments.deployColumns] +deployedBy = "Deployed by" +environment = "Environment" +product = "Product" +status = "Status" +version = "Version" +when = "When" + +[portal.infrastructure.deployments.recent] +heading = "Recent deployments" +subheading = "The latest rollouts across products and environments." + +[portal.infrastructure.deployments.regionColumns] +instances = "Instances" +latency = "Latency" +load = "Load" +p99 = "P99" +region = "Region" +status = "Status" +throughput = "Throughput" +uptime = "Uptime" +version = "Version" + +[portal.infrastructure.deployments.regions] +heading = "Regions" +subheading = "Live health for every deployed Stirling region — latency, load, and rollout version." + +[portal.infrastructure.deployments.regions.empty] +description = "Deployed regions appear here once your workspace is provisioned." +title = "No regions deployed" + +[portal.infrastructure.models] +heading = "Models" +msValue = "{{value}} ms" +subheading = "The model catalogue and routing that powers document processing across your workspace." + +[portal.infrastructure.models.byom] +description = "Register an on-prem or self-hosted model and pin it to a region for data-residency-bound processing." +title = "Bring your own model" + +[portal.infrastructure.models.catalogue] +heading = "Catalogue" +sub = "Managed models available to your workspace, with live latency and cost." +subEnterprise = "Managed, bring-your-own, and on-prem models — with per-region pinning available." + +[portal.infrastructure.models.catalogue.empty] +description = "Models in your workspace's catalogue appear here." +title = "No models available" + +[portal.infrastructure.models.columns] +cost = "Cost" +latency = "Latency" +load = "Load" +model = "Model" +status = "Status" +type = "Type" +version = "Version" + +[portal.infrastructure.models.metrics] +activeModels = "Active models" +avgLatency = "Avg latency" +included = "Included" +monthlySpend = "Monthly model spend" + +[portal.infrastructure.models.routing] +empty = "No routing rules configured." +heading = "Routing rules" +sub = "Which model handles each operation. The default applies when no narrower rule matches." +subLocked = "Route operations to specific models — available on paid plans." + +[portal.infrastructure.models.routing.lockedBanner] +description = "Upgrade to Pro to control which model handles each operation and document type." +title = "Model routing is a paid feature" + +[portal.infrastructure.models.routingColumns] +default = "Default" +docType = "Document type" +modelForAria = "Model for {{operation}}" +operation = "Operation" +routedTo = "Routed to" + +[portal.infrastructure.security.access.byok] +description = "Supply a key from your own KMS. Stirling encrypts with it but can still read." +label = "Bring your own key (BYOK)" + +[portal.infrastructure.security.access.hyok] +description = "Keys never leave your KMS. Stirling holds only ciphertext." +label = "Hold your own key (HYOK)" + +[portal.infrastructure.security.access.stirling] +description = "Stirling manages encryption keys. Simplest — zero key ops on your side." +label = "Stirling-held keys" + +[portal.infrastructure.security.accessPolicy] +heading = "Document access policy" +subheading = "Controls who can decrypt processed documents at rest." + +[portal.infrastructure.security.attestations] +heading = "Compliance attestations" +noReport = "No report available" +subheading = "Framework-by-framework audit posture, with reports available on attested controls." +viewReport = "View report →" + +[portal.infrastructure.security.compliance] +heading = "Compliance" +subheading = "Attestations and certifications covering the Stirling platform." + +[portal.infrastructure.security.empty] +description = "Your workspace's security configuration will appear here." +title = "Security posture unavailable" + +[portal.infrastructure.security.hyokBanner] +description = "With HYOK, encryption keys never leave your KMS. Stirling stores and processes only ciphertext you can revoke at any time." +title = "Stirling cannot decrypt your documents" + +[portal.infrastructure.security.ipAllowlist] +empty = "No IP ranges configured — all IPs allowed." +heading = "IP allowlist" +sub = "API access is restricted to these CIDR ranges." +subLocked = "Restrict API access to known IP ranges — available on paid plans." + +[portal.infrastructure.security.ipAllowlist.lockedBanner] +description = "Upgrade to Pro to restrict API access to specific networks." +title = "IP allowlisting is a paid feature" + +[portal.infrastructure.security.ipColumns] +added = "Added" +addedBy = "Added by" +cidr = "CIDR" +label = "Label" + +[portal.infrastructure.security.keyManagement] +algorithm = "Algorithm" +heading = "Encryption key management" +keyId = "Key identifier" +lastRotated = "Last rotated" +rotateKey = "Rotate key" +rotationPolicy = "Rotation policy" +subheading = "Custody of the keys that encrypt documents at rest — who can decrypt, and how keys rotate." + +[portal.infrastructure.security.managedBanner] +description = "Bring-your-own-key (BYOK) and hold-your-own-key (HYOK) custody are available on Enterprise. Upgrade to supply keys from your own KMS." +title = "Keys are managed by Stirling on your plan" + +[portal.infrastructure.security.residency.apac] +description = "ap-southeast-1" +label = "Asia Pacific" + +[portal.infrastructure.security.residency.eu] +description = "eu-west-1 · GDPR data boundary" +label = "European Union" + +[portal.infrastructure.security.residency.us] +description = "us-east-1 · us-west-2" +label = "United States" + +[portal.infrastructure.security.residencyHeader] +heading = "Data residency" +subheading = "Where documents are stored and processed." + +[portal.infrastructure.storage] +gbValue = "{{value}} GB" +percentUsed = "{{value}} used" + +[portal.infrastructure.storage.empty] +description = "Connected storage and usage appear here." +title = "No storage configured" + +[portal.infrastructure.storage.lifecycle] +active = "Active" +activeRange = "0–{{value}}d" +archived = "Archived" +coldStorage = "cold storage" +deleted = "Deleted" +never = "never" +purged = "purged" + +[portal.infrastructure.storage.providers] +connect = "Connect" +connected = "Connected" +heading = "Connected providers" +subheading = "Where processed artifacts are written." + +[portal.infrastructure.storage.retention] +heading = "Retention" +subheading = "How long artifacts are kept before lifecycle deletion." +windowLabel = "Default retention window" + +[portal.infrastructure.storage.retentionOption] +days_one = "{{count}} day" +days_other = "{{count}} days" +never = "Never delete" + +[portal.infrastructure.storage.totalUsage] +heading = "Total usage" +progressLabel = "Storage used" +subheading = "Storage consumed across all connected providers." + +[portal.infrastructure.tabs] +apiKeys = "API Keys" +audit = "Audit Logs" +deployments = "Deployments" +models = "Models" +security = "Security" +storage = "Storage" + +[portal.mocks.label] +off = "Mocks OFF" +on = "Mocks ON" + +[portal.mocks.tooltip] +off = "Mock data OFF — fetch calls go to the real network. Click to re-enable mocks (reloads the page)." +on = "Mock data ON — fetch calls are intercepted by MSW. Click to switch to the real network (reloads the page)." + +[portal.nav] +agent-builder = "Agent Builder" +components = "Components" +docs = "Developer Docs" +documents = "Documents" +editor = "Editor" +home = "Home" +infrastructure = "Infrastructure" +pipelines = "Pipelines" +policies = "Policies" +procurement = "Procurement" +settings = "Settings" +sources = "Sources" +usage = "Usage & Billing" +users = "Users" + +[portal.notifications] +markAllRead = "Mark all read" +title = "Notifications" +viewAll = "View all" + +[portal.notifications.ariaLabel] +none = "Notifications, no unread" +unread_one = "Notifications, {{count}} unread" +unread_other = "Notifications, {{count}} unread" + +[portal.notifications.count] +allRead = "all read" +loading = "loading" +new_one = "{{count}} new" +new_other = "{{count}} new" + +[portal.notifications.empty] +description = "No new notifications." +title = "You're all caught up" + +[portal.opRunner] +durationMs = "{{ms}} ms" +featuredOps = "Featured ops" +subtitle = "Drop a sample, pick an op, see what Stirling returns." +title = "Try a PDF operation" + +[portal.opRunner.action] +close = "Close" +openBuilder = "Open the pipeline builder" +run = "Run operation" +runAgain = "Run again" +running = "Running…" + +[portal.opRunner.drop] +hint = "or use a sample document." +pickAnother = "Pick another sample" +replaceHint = "Drop again or pick another sample to replace." +title = "Drop a PDF here" +useSample = "Use a sample" + +[portal.opRunner.empty] +description = "Once operations are published, they'll show up here." +title = "No featured ops yet" + +[portal.opRunner.error] +title = "The operation didn't complete" +unknown = "Unknown error" + +[portal.opRunner.hint] +aSample = "a sample" +press = "Press" +ready = "Ready" +toInvoke = "to invoke" + +[portal.opRunner.hint.runOn] +before = "Run" +middle = "on" + +[portal.opRunner.running] +title = "Running {{label}}…" + +[portal.opRunner.status] +completed = "Completed" +failed = "Failed" + +[portal.pipelines] +subtitle = "Every automated document pipeline on the backend: an ordered chain of operations over a set of sources, run on a trigger. Click a row for its steps and sources." +title = "Pipelines" + +[portal.pipelines.actions] +newPipeline = "New pipeline" + +[portal.pipelines.composer] +cancel = "Cancel" +chainEmpty = "Add operations from the palette below." +create = "Create pipeline" +directory = "Output folder" +directoryHelp = "Absolute path on the server. Must be within the configured allowed folders." +editTitle = "Edit pipeline" +moveDown = "Move down" +moveUp = "Move up" +name = "Name" +namePlaceholder = "e.g. Redaction sweep" +noSources = "No sources connected yet. The pipeline can still run on files supplied to it directly." +operations_one = "Operation ({{count}})" +operations_other = "Operations ({{count}})" +output = "Output" +removeStep = "Remove operation" +save = "Save changes" +scheduleEvery = "Run every" +sources = "Sources" +sourcesLoading = "Loading sources..." +subtitle = "Pick the sources it runs over, chain the operations, then choose when it runs and where output goes." +title = "New pipeline" +trigger = "Trigger" +triggerManual = "Manual only" + +[portal.pipelines.composer.unit] +days = "days" +hours = "hours" +minutes = "minutes" + +[portal.pipelines.delete] +body = "Delete \"{{name}}\"? This can't be undone." +cancel = "Cancel" +confirm = "Delete" +title = "Delete pipeline?" + +[portal.pipelines.detail] +closeAriaLabel = "Close detail" +delete = "Delete pipeline" +edit = "Edit" +noSources = "No sources. Files are supplied directly to each run." +noSteps = "No operations configured." +output = "Output" +pause = "Pause" +resume = "Resume" +run = "Run now" +sources = "Sources" +steps = "Operations" +subtitle = "{{trigger}} · {{status}}" + +[portal.pipelines.empty] +action = "Create a pipeline" +description = "Create your first pipeline: pick the sources it runs over, chain the operations, and choose where output goes." +title = "No pipelines yet" + +[portal.pipelines.kpi] +active = "Active" +paused = "Paused" +total = "Pipelines" + +[portal.pipelines.output] +folder = "Write to folder" +inline = "Return files" + +[portal.pipelines.run] +completed_one = "Run completed." +completed_other = "All {{count}} runs completed." +empty = "Nothing to run: the sources had no documents to process." +failed = "Run failed: {{error}}" +running = "Run started; still in progress." + +[portal.pipelines.status] +active = "Active" +paused = "Paused" + +[portal.pipelines.table] +name = "Pipeline" +sources = "Sources" +status = "Status" +steps = "Steps" + +[portal.pipelines.trigger] +folder-watch = "Folder watch" +manual = "Manual" +schedule = "Scheduled" + +[portal.policies] +subtitle = "Standing automations that enforce a tool pipeline on every document. Each policy fires on upload or export, runs its tool chain, and saves the enforced version alongside the original." +title = "Policies" + +[portal.policies.card] +comingSoon = "Coming soon" +notSetUp = "Not set up" + +[portal.policies.detail] +enforces = "Enforces" +onEveryExport = "On every export" +onEveryUpload = "On every upload" +outputAsNewFile = "as a new file" +outputAsNewVersion = "as a new version" +recentActivity = "Recent activity" +retry = "Retry" +showLess = "Show less" +showMore = "Show more" +sources = "Sources" + +[portal.policies.detail.actions] +delete = "Delete" +editSettings = "Edit settings" +pause = "Pause" +resume = "Resume" +runNow = "Run now" + +[portal.policies.detail.emptyActivity] +description = "Documents will appear here once this policy runs." +title = "No activity yet" + +[portal.policies.offline] +description = "Your policies are saved and will appear once the connection is restored." +retry = "Retry" +title = "Backend unavailable" + +[portal.policies.stats] +activeFor = "Active" +dataProcessed = "Data processed" +docsEnforced = "Docs enforced" + +[portal.policies.status] +active = "Active" +paused = "Paused" + +[portal.policies.summary.active] +description = "Enforcing on upload/export" +label = "Active policies" + +[portal.policies.summary.categories] +description = "Available to configure" +label = "Categories" + +[portal.policies.summary.docsEnforced] +description = "Across active policies" +label = "Docs enforced" + +[portal.policies.summary.paused] +description = "Configured but not firing" +label = "Paused" + +[portal.policies.wizard.actions] +back = "Back" +cancel = "Cancel" +continue = "Continue" +enablePolicy = "Enable policy" +saveChanges = "Save changes" + +[portal.policies.wizard.docTypes] +allDescription = "Set up an Ingestion (classification) policy to narrow this to specific document types." +allTitle = "All document types" +clear = "Clear" +heading = "Document types" +narrow = "Narrow" +selected_one = "{{count}} selected" +selected_other = "{{count}} selected" + +[portal.policies.wizard.errors] +noTools = "Enable at least one tool in the workflow first." +saveFailed = "Couldn't save the policy. Please try again." + +[portal.policies.wizard.output] +heading = "Output & run" + +[portal.policies.wizard.output.filenameRule] +autoNumber = "Auto-number" +label = "Filename rule" +placeholder = "Text to add (optional)" +prefix = "Prefix" +suffix = "Suffix" + +[portal.policies.wizard.output.outputAs] +label = "Output as" +newFile = "New file" +newVersion = "New version" + +[portal.policies.wizard.output.retries] +delayLabel = "Retry delay (min)" +heading = "Retries" +maxLabel = "Max retries" + +[portal.policies.wizard.output.runOn] +export = "Export" +helper = "When the policy fires: on upload, or before export." +label = "Run on" +upload = "Upload" + +[portal.policies.wizard.settings] +heading = "Settings" + +[portal.policies.wizard.sources] +emptyDescription = "Connect a source on the Sources page first, then attach it to a policy here." +emptyTitle = "No sources available" +heading = "Sources" +loading = "Loading sources…" + +[portal.policies.wizard.tabs] +ariaLabel = "Setup steps" +settings = "Settings" +workflow = "Workflow" + +[portal.policies.wizard.title] +edit = "Edit {{category}} policy" +setUp = "Set up {{category}} policy" + +[portal.policies.wizard.workflow] +description = "The sequence of tools this policy runs on each document. Each tool is a Stirling endpoint; toggle the ones this policy should enforce." + +[portal.policySummary] +activeSummary = "{{active}} / {{total}} active" +noRule = "No rule enforced yet" +subtitle = "Standing automations every document passes through, regardless of which pipeline handles it." +title = "What runs on your PDFs" + +[portal.policySummary.action] +comingSoon = "Coming soon" +configure = "Configure" +setUp = "Set up" + +[portal.policySummary.column] +activeRule = "Active rule" +policy = "Policy" +status = "Status" + +[portal.policySummary.empty] +description = "Once policies are configured, the categories appear here." +title = "No policies yet" + +[portal.policySummary.state] +active = "Active" +off = "Off" +soon = "Soon" + +[portal.processingStatus] +managePlan = "Manage plan" +pdfsThisMonth = "PDFs this month" +progressLabel = "{{used}} of {{cap}} PDFs used this month" +upgrade = "Upgrade" +volumeSuffix = "PDFs processed · last 30 days" + +[portal.procurement] +enterpriseBadge = "Enterprise" +subtitle = "Get your team evaluated, contracted, and onboarded. Every document in one place." +title = "Procurement" + +[portal.procurement.action] +download = "Download" +pay = "Pay now" +request = "Request" +sign = "Review & sign" +upload = "Upload" + +[portal.procurement.docs] +count_one = "{{count}} doc" +count_other = "{{count}} docs" +done = "Done" +here = "You're here" +hide = "Hide" +optional = "Optional" +paidAddon = "Paid add-on" +show = "Show" +subtitle = "Everything you need at each step of the journey, surfaced as the deal moves through it." +supportingSubtitle = "SOC 2, security reviews, tax forms and more, ready when your security or procurement team asks. Some carry a one-time fee." +supportingTitle = "Supporting your evaluation" +title = "Documents" +upcoming = "Upcoming" + +[portal.procurement.journey] +daysLeft_one = "{{count}} day left" +daysLeft_other = "{{count}} days left" +engineerLabel = "Your solutions engineer" +eyebrow = "Your rollout" +live = "You're live on Stirling Enterprise" +nextStep = "Next step: {{action}}" +subtitle = "Your solutions engineer is on every step. One next action at a time; the full checklist is below." +title = "From trial to live, one guided path" +trialTitle = "Enterprise trial" + +[portal.procurement.locked] +description = "Trial keys, committed-volume quotes, the one-signature agreement, payment, and your document ledger all live here once you start an enterprise evaluation." +eyebrow = "Enterprise only" +talkToSales = "Talk to sales" +title = "The procurement track opens with Enterprise" + +[portal.procurement.modal] +cancel = "Cancel" +chooseFile = "Choose file" +downloadBody = "Your download will begin shortly." +downloadCta = "Download" +downloadTitle = "Download" +noFile = "No file selected" +payBody = "Pay your committed contract by card or bank transfer through Stripe. Your workspace provisions as soon as payment clears." +payCta = "Continue to Stripe" +payTitle = "Confirm payment" +requestBodyFree = "We generate this on demand. Confirm and your solutions engineer will send it across shortly." +requestBodyPaid = "This is a paid add-on. Confirm and your solutions engineer will scope it and send the paperwork." +requestCta = "Request" +requestTitle = "Request this document" +signBody = "Opens the Stirling Enterprise Agreement for e-signature: one signature covers the MSA, order form, EULA and DPA. We countersign automatically and you advance to payment." +signCta = "Open for signature" +signTitle = "Review and sign your agreement" +uploadBody = "Send us your PO and we invoice against it on your terms. Drag in the PDF or pick a file below." +uploadCta = "Upload purchase order" +uploadTitle = "Upload your purchase order" + +[portal.procurement.status] +action = "Action needed" +available = "Available" +complete = "Complete" +pending = "Pending" +request = "On request" + +[portal.recentActivity] +title = "Recent activity" +viewAll = "View all" + +[portal.recentActivity.empty] +description = "Pipeline runs, deploys and agent events will appear here." +title = "Nothing here yet" + +[portal.search] +ariaLabel = "Search" +placeholder = "Search Stirling — endpoints, pipelines, docs…" + +[portal.search.empty] +noActionsDescription = "Quick actions will appear here once they're available." +noActionsTitle = "No quick actions" +noMatches = "No matches for \"{{query}}\"" +noMatchesDescription = "Try a different keyword or browse the catalogue." + +[portal.settings] +ariaLabel = "Settings" +cancel = "Cancel" +enterpriseBadge = "Enterprise" +footerNote = "Changes apply to this workspace." +saveChanges = "Save changes" + +[portal.settings.appearance] +themeSub = "Choose how the portal looks on this device." +themeTitle = "Theme" + +[portal.settings.appearance.dark] +hint = "Dim surfaces" +label = "Dark" + +[portal.settings.appearance.light] +hint = "Bright surfaces" +label = "Light" + +[portal.settings.authentication] +sessionTimeout = "Session timeout" +sessionTimeoutHelper = "Members re-authenticate after this idle period." +sub = "Organisation-wide authentication controls." +title = "Sign-in policy" + +[portal.settings.authentication.mfa] +description = "Require every member to complete MFA at sign-in." +label = "Enforce two-factor (MFA)" + +[portal.settings.authentication.scim] +description = "Sync members and roles from your directory." +label = "SCIM provisioning" + +[portal.settings.authentication.sso] +description = "Federate sign-in through your identity provider." +label = "Single sign-on (SAML)" + +[portal.settings.authentication.timeout] +1440 = "24 hours" +240 = "4 hours" +480 = "8 hours" +60 = "1 hour" +720 = "12 hours" + +[portal.settings.earlyAccess] +sub = "Opt into features still in preview." +title = "Preview features" + +[portal.settings.groups] +account = "Account" +admin = "Admin" +workspace = "Workspace" + +[portal.settings.notifications] +sub = "Pick which events reach your inbox." +title = "Email notifications" + +[portal.settings.notifications.pipeline-failures] +description = "A run errors out or a step times out." +label = "Pipeline failures" + +[portal.settings.notifications.pipeline-success] +description = "Every successful pipeline run finishes." +label = "Pipeline completions" + +[portal.settings.notifications.product-updates] +description = "New operations, sources, and release notes." +label = "Product updates" + +[portal.settings.notifications.security-alerts] +description = "New API keys, sign-ins, or permission changes." +label = "Security alerts" + +[portal.settings.notifications.usage-alerts] +description = "You approach a plan limit or rate cap." +label = "Usage & quota alerts" + +[portal.settings.notifications.weekly-digest] +description = "A Monday summary of volume and health." +label = "Weekly digest" + +[portal.settings.profile] +accountFallback = "Account" +changePhoto = "Change photo" +email = "Email" +emailHelper = "Used for sign-in and notification delivery." +emailPlaceholder = "you@company.com" +fullName = "Full name" +namePlaceholder = "Your name" + +[portal.settings.sections] +account-link = "Account link" +appearance = "Appearance" +authentication = "Authentication" +early-access = "Early access" +general = "General" +notifications = "Notifications" +profile = "Profile" +sessions = "Active sessions" + +[portal.settings.sessions] +revoke = "Revoke" +sub = "Devices currently signed in to this account." +thisDevice = "This device" +title = "Active sessions" + +[portal.settings.workspace] +manageBilling = "Manage billing" +nameLabel = "Workspace name" +namePlaceholder = "Workspace name" +plan = "Plan" +regionEnterpriseSuffix = "{{region}} · Enterprise" +regionHelper = "Where documents are processed and stored at rest." +regionLabel = "Data residency region" +seats = "Seats" +seatsUsed = "{{used}} of {{total}} used" + +[portal.shell.header] +accountFallback = "Account" +accountMenu = "Account menu" +darkMode = "Dark mode" +lightMode = "Light mode" +search = "Search" +searchPlaceholder = "Search…" +signOut = "Sign out" +switchToDark = "Switch to dark theme" +switchToLight = "Switch to light theme" + +[portal.shell.sidebar] +appEditor = "Editor" +appProcessor = "Processor" +brandSuffix = "Stirling Processor" +docsCount = "{{docs}} docs" +docsProcessed = "Docs processed" +linkAccount = "Link Stirling account" +planEnterprise = "Enterprise plan" +planProcessor = "Processor plan" +primaryNav = "Primary navigation" +switchApp = "Switch app" + +[portal.sources] +subtitle = "Reusable input connections that feed documents into Stirling. Configure a connection once, then reference it from any number of policies. Click a row for its config and which policies use it." +title = "Sources" + +[portal.sources.actions] +agentBuilder = "Agent Builder" +connectSource = "Connect source" + +[portal.sources.delete] +body = "Delete \"{{name}}\"? This can't be undone. Policies that reference it would need to be updated." +cancel = "Cancel" +confirm = "Delete" +title = "Delete source?" + +[portal.sources.detail] +closeAriaLabel = "Close detail" +delete = "Delete source" +docs24h = "Last 24h" +docs30d = "Last 30 days" +docsTotal = "Total seen" +docsTrend = "Documents over the last 30 days" +documents = "Documents" +edit = "Edit" +notReferenced = "Not referenced by any policy, so it's safe to delete." +pause = "Pause" +resume = "Resume" +subtitle = "{{type}} · {{status}}" +usedBy = "Used by" + +[portal.sources.empty] +description = "Connect a folder (and, soon, cloud storage) so your policies have somewhere to pull documents from." +title = "No sources connected yet" + +[portal.sources.kpi] +inUse = "In use" +total = "Connections" +unused = "Unused" + +[portal.sources.status] +active = "Active" +disabled = "Disabled" +unused = "Unused" + +[portal.sources.table] +source = "Source" +status = "Status" +usedBy = "Policies" + +[portal.sources.types.editor] +label = "Editor" + +[portal.sources.types.folder] +description = "Watch a directory on the server for new documents." +label = "Folder" + +[portal.sources.types.folder.fields.directory] +helperText = "Absolute path Stirling watches for files to process." +label = "Directory path" +placeholder = "/data/incoming" + +[portal.sources.types.folder.fields.mode] +label = "Read mode" + +[portal.sources.types.folder.fields.mode.options] +consume = "Consume: process each file once" +snapshot = "Snapshot: re-read the folder every run" + +[portal.sources.types.unknown] +label = "Source" + +[portal.sources.wizard] +back = "Back" +cancel = "Cancel" +continue = "Continue" +editTitle = "Edit source" +name = "Name" +namePlaceholder = "e.g. Claims intake" +save = "Save changes" +subtitle = "Step {{current}} of {{total}} · {{label}}" +title = "Connect a source" +type = "Type" + +[portal.sources.wizard.steps] +chooseType = "Choose type" +configure = "Configure" +review = "Review & connect" + +[portal.usage] +managePayment = "Manage Payment" +subtitle = "Consumption, invoices, and plan management for every PDF Stirling has billed, in one console." +title = "Usage & billing" + +[portal.usage.error] +loadWallet = "Couldn't load wallet" +openStripePortal = "Couldn't open Stripe portal" +walletUnavailable = "Wallet unavailable: {{status}} {{statusText}}" + +[portal.usage.finalizing] +body = "It can take a few seconds for your subscription to activate. This page updates automatically." +title = "Finalizing your subscription…" + +[portal.usage.sessionExpired] +action = "Sign in again" +body = "Your Stirling account session has expired. Sign in again to view billing — your instance stays linked." +title = "Session expired" + +[portal.usageChart] +defaultLabel = "Docs processed · last 30 days" +delta = "{{pct}}% vs prior 30d" +docsValue = "{{value}} docs" +srAnnounce = "{{date}}: {{value}} docs" + +[portal.useCases] +title = "Popular use cases" +viewAll = "View all pipelines" + +[portal.useCases.items.authenticity] +blurb = "Cryptographic checks at the document boundary — signature validation, tamper detection, signing flows for outbound documents. Trust decisions in the pipeline, not your app code." +cta = "Try authenticity check" +eyebrow = "AUTHENTICITY" +title = "Verify signatures and detect tampering" + +[portal.useCases.items.autoRouting] +blurb = "One classifier reads what arrived — KYC form, invoice, contract, COI — and routes to the right downstream pipeline. No manual triage, no docs in the wrong workflow." +cta = "Build a classifier pipeline" +eyebrow = "AUTO-ROUTING" +title = "Auto-classify and route incoming documents" + +[portal.useCases.items.piiRedaction] +blurb = "Strip sensitive fields before storage, indexing, or LLM processing. Schema-aware, per-field audit, BYOK or HYOK keys. Compliance at the document boundary, not per pipeline." +cta = "See redaction pipelines" +eyebrow = "PII REDACTION" +title = "Redact PII before it leaves your stack" + +[portal.useCases.items.trainingData] +blurb = "Batch-import an archive, redact PII, classify, chunk, and emit ready-to-load JSON for fine-tuning, eval sets, or RAG. Self-completing and replayable." +cta = "Build a training-data pipeline" +eyebrow = "TRAINING DATA" +title = "Turn PDFs into training data" + +[portal.users] +subtitle = "The people in your organization and the access they hold — roles, status and security controls." +title = "Users" + +[portal.users.access] +subtitle = "Seats, authentication and provisioning for your organization." +title = "Access & security" + +[portal.users.access.auth] +title = "Authentication" + +[portal.users.access.auth.requireMfa] +description = "Members must set up a second factor to sign in." +enforced = "Enforced org-wide on this plan." +label = "Require MFA" + +[portal.users.access.auth.shortSessions] +description = "Sign members out after inactivity (currently {{timeout}})." +label = "Short-lived sessions" + +[portal.users.access.scim] +active = "Active" +directory = "Directory" +lastSync = "Last sync" +note = "Members are created, updated and deactivated automatically from your identity provider." +off = "Off" +title = "SCIM provisioning" + +[portal.users.access.seats] +title = "Seats" +unlimited = "Your plan includes unlimited seats." +usedLabel = "{{used}} of {{limit}} seats used" + +[portal.users.access.sso] +connected = "Connected" +domains = "Domains" +manage = "Manage connection" +notConfigured = "Not configured" +provider = "Provider" +title = "SSO / SAML" + +[portal.users.access.upgrade] +action = "Upgrade plan" +title = "Unlock team access controls" + +[portal.users.empty] +description = "Invite your team to start collaborating on documents and pipelines." +title = "No members yet" + +[portal.users.invite] +cancel = "Cancel" +email = "Email" +emailError = "Enter a valid email address" +emailPlaceholder = "teammate@acme.com" +role = "Role" +roleHelper = "Determines what the member can do once they join." +send = "Send invite" +subtitle = "They'll receive an email to join your organization." + +[portal.users.roles] +subtitle = "Every role exists on every plan — what each one can do is fixed across the org." +title = "Roles" + +[portal.users.summary] +members = "Members" +pendingInvites = "Pending invites" +seatsUsed = "Seats used" + +[portal.users.table] +actionsFor = "Actions for {{name}}" +changeRole = "Change role" +lastActive = "Last active" +member = "Member" +remove = "Remove from org" +role = "Role" +status = "Status" +suspend = "Suspend" + +[portal.welcome] +ariaLabel = "Stirling product highlights" +pagination = "Carousel pagination" +slideLabel = "Slide {{number}}: {{title}}" + +[portal.welcome.ornament.editor] +critical = "Critical" +ocrClean = "OCR-clean" +schemaMatch = "schema match 0.97" +signed = "signed" + +[portal.welcome.slides.agents] +eyebrow = "AI Agents" +primary = "Try PDF Processor" +secondary = "View MCP docs" +sub = "Wire your agent via MCP, REST or tool definitions. Deterministic operations and guardrails — test with scenarios and evals before you ship." +title = "PDF Processor for AI Agents" + +[portal.welcome.slides.editor] +eyebrow = "PDF Editor" +primary = "Install PDF Editor" +secondary = "Connect an instance" +sub = "Annotate, sign, redact, and review locally or in the cloud. Brought to the platform as the credibility anchor of the Stirling control plane." +title = "The #1 PDF Editor on GitHub" + +[portal.welcome.slides.platform] +eyebrow = "Platform" +primary = "Try a PDF operation" +secondary = "Get an API key" +sub = "Ingest from agents, APIs and connectors. Run composable pipelines with evals and golden sets. Land in a vault with zero-standing-access controls." +title = "PDF Infrastructure for Developers" + [printFile] title = "Print File" diff --git a/frontend/portal/public/mockServiceWorker.js b/frontend/editor/public/mockServiceWorker.js similarity index 100% rename from frontend/portal/public/mockServiceWorker.js rename to frontend/editor/public/mockServiceWorker.js diff --git a/frontend/editor/src/cloud/components/shared/config/configSections/SpendCapControl.tsx b/frontend/editor/src/cloud/components/shared/config/configSections/SpendCapControl.tsx index f26d40f743..654691c2fc 100644 --- a/frontend/editor/src/cloud/components/shared/config/configSections/SpendCapControl.tsx +++ b/frontend/editor/src/cloud/components/shared/config/configSections/SpendCapControl.tsx @@ -1,5 +1,5 @@ /** - * Editor cloud adapter over the shared {@code @shared/billing} spend-cap control: + * Editor cloud adapter over the shared {@code @app/billing} spend-cap control: * supplies the i18n copy (the shared control is copy-agnostic) and the editor's * {@code scc-*} styling. The public API (controlled {@code capUsd}/{@code * onChange}, optional {@code onSave}/{@code saveLabel}, {@code note}) is @@ -11,7 +11,7 @@ import { useTranslation } from "react-i18next"; import { DEFAULT_CAP_PRESETS, SpendCapControl as SharedSpendCapControl, -} from "@shared/billing"; +} from "@app/billing"; // eslint-disable-next-line no-restricted-imports import "./SpendCapControl.css"; diff --git a/frontend/editor/src/cloud/components/shared/config/configSections/usageMeters.tsx b/frontend/editor/src/cloud/components/shared/config/configSections/usageMeters.tsx index 308fc97d32..4c8804027c 100644 --- a/frontend/editor/src/cloud/components/shared/config/configSections/usageMeters.tsx +++ b/frontend/editor/src/cloud/components/shared/config/configSections/usageMeters.tsx @@ -7,7 +7,7 @@ import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { useWallet, type Wallet } from "@app/hooks/useWallet"; -import { currencySymbol, MeterBar, meterState } from "@shared/billing"; +import { currencySymbol, MeterBar, meterState } from "@app/billing"; import "@app/components/shared/config/configSections/Payg.css"; import "@app/components/shared/config/configSections/PaygFree.css"; diff --git a/frontend/editor/src/cloud/hooks/useWallet.ts b/frontend/editor/src/cloud/hooks/useWallet.ts index 5a1b1d21cb..07e862fb8c 100644 --- a/frontend/editor/src/cloud/hooks/useWallet.ts +++ b/frontend/editor/src/cloud/hooks/useWallet.ts @@ -57,10 +57,10 @@ import type { WalletMember, WalletCategoryBreakdown, WalletActivityRow, -} from "@shared/billing"; +} from "@app/billing"; // ─── Public types ─────────────────────────────────────────────────────── -// The wallet contract lives in @shared/billing (shared with the admin portal). +// The wallet contract lives in @app/billing (shared with the admin portal). // Re-exported so existing `@app/hooks/useWallet` importers keep their imports. export type { Wallet, diff --git a/frontend/editor/src/cloud/tsconfig.json b/frontend/editor/src/cloud/tsconfig.json index bf3eedb2e5..2b703ea335 100644 --- a/frontend/editor/src/cloud/tsconfig.json +++ b/frontend/editor/src/cloud/tsconfig.json @@ -7,10 +7,10 @@ "../../src/proprietary/*", "../../src/core/*" ], + "@portal/*": ["../../src/portal/*"], "@cloud/*": ["../../src/cloud/*"], "@proprietary/*": ["../../src/proprietary/*"], - "@core/*": ["../../src/core/*"], - "@shared/*": ["../../../shared/*"] + "@core/*": ["../../src/core/*"] } }, "include": [ diff --git a/frontend/shared/assets/Brand.stories.tsx b/frontend/editor/src/core/assets/Brand.stories.tsx similarity index 75% rename from frontend/shared/assets/Brand.stories.tsx rename to frontend/editor/src/core/assets/Brand.stories.tsx index 87cf9f1257..886e7a023d 100644 --- a/frontend/shared/assets/Brand.stories.tsx +++ b/frontend/editor/src/core/assets/Brand.stories.tsx @@ -2,16 +2,16 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; // Visual catalogue of the shared brand assets (shared/assets/brand) — the single // source of truth for the Stirling logos used across the apps. -import modernMarkDark from "@shared/assets/brand/modern-logo/StirlingPDFLogoNoTextDark.svg"; -import modernMarkLight from "@shared/assets/brand/modern-logo/StirlingPDFLogoNoTextLight.svg"; -import modernBlack from "@shared/assets/brand/modern-logo/StirlingPDFLogoBlackText.svg"; -import modernWhite from "@shared/assets/brand/modern-logo/StirlingPDFLogoWhiteText.svg"; -import modernGrey from "@shared/assets/brand/modern-logo/StirlingPDFLogoGreyText.svg"; -import classicMarkDark from "@shared/assets/brand/classic-logo/StirlingPDFLogoNoTextDark.svg"; -import classicMarkLight from "@shared/assets/brand/classic-logo/StirlingPDFLogoNoTextLight.svg"; -import classicBlack from "@shared/assets/brand/classic-logo/StirlingPDFLogoBlackText.svg"; -import classicWhite from "@shared/assets/brand/classic-logo/StirlingPDFLogoWhiteText.svg"; -import classicGrey from "@shared/assets/brand/classic-logo/StirlingPDFLogoGreyText.svg"; +import modernMarkDark from "@app/assets/brand/modern-logo/StirlingPDFLogoNoTextDark.svg"; +import modernMarkLight from "@app/assets/brand/modern-logo/StirlingPDFLogoNoTextLight.svg"; +import modernBlack from "@app/assets/brand/modern-logo/StirlingPDFLogoBlackText.svg"; +import modernWhite from "@app/assets/brand/modern-logo/StirlingPDFLogoWhiteText.svg"; +import modernGrey from "@app/assets/brand/modern-logo/StirlingPDFLogoGreyText.svg"; +import classicMarkDark from "@app/assets/brand/classic-logo/StirlingPDFLogoNoTextDark.svg"; +import classicMarkLight from "@app/assets/brand/classic-logo/StirlingPDFLogoNoTextLight.svg"; +import classicBlack from "@app/assets/brand/classic-logo/StirlingPDFLogoBlackText.svg"; +import classicWhite from "@app/assets/brand/classic-logo/StirlingPDFLogoWhiteText.svg"; +import classicGrey from "@app/assets/brand/classic-logo/StirlingPDFLogoGreyText.svg"; type Asset = { label: string; src: string; onDark?: boolean }; type VariantSet = { variant: string; mark: Asset[]; wordmark: Asset[] }; diff --git a/frontend/shared/assets/brand/classic-logo/Firstpage.png b/frontend/editor/src/core/assets/brand/classic-logo/Firstpage.png similarity index 100% rename from frontend/shared/assets/brand/classic-logo/Firstpage.png rename to frontend/editor/src/core/assets/brand/classic-logo/Firstpage.png diff --git a/frontend/shared/assets/brand/classic-logo/StirlingPDFLogoBlackText.svg b/frontend/editor/src/core/assets/brand/classic-logo/StirlingPDFLogoBlackText.svg similarity index 100% rename from frontend/shared/assets/brand/classic-logo/StirlingPDFLogoBlackText.svg rename to frontend/editor/src/core/assets/brand/classic-logo/StirlingPDFLogoBlackText.svg diff --git a/frontend/shared/assets/brand/classic-logo/StirlingPDFLogoGreyText.svg b/frontend/editor/src/core/assets/brand/classic-logo/StirlingPDFLogoGreyText.svg similarity index 100% rename from frontend/shared/assets/brand/classic-logo/StirlingPDFLogoGreyText.svg rename to frontend/editor/src/core/assets/brand/classic-logo/StirlingPDFLogoGreyText.svg diff --git a/frontend/shared/assets/brand/classic-logo/StirlingPDFLogoNoTextDark.svg b/frontend/editor/src/core/assets/brand/classic-logo/StirlingPDFLogoNoTextDark.svg similarity index 100% rename from frontend/shared/assets/brand/classic-logo/StirlingPDFLogoNoTextDark.svg rename to frontend/editor/src/core/assets/brand/classic-logo/StirlingPDFLogoNoTextDark.svg diff --git a/frontend/shared/assets/brand/classic-logo/StirlingPDFLogoNoTextLight.svg b/frontend/editor/src/core/assets/brand/classic-logo/StirlingPDFLogoNoTextLight.svg similarity index 100% rename from frontend/shared/assets/brand/classic-logo/StirlingPDFLogoNoTextLight.svg rename to frontend/editor/src/core/assets/brand/classic-logo/StirlingPDFLogoNoTextLight.svg diff --git a/frontend/shared/assets/brand/classic-logo/StirlingPDFLogoWhiteText.svg b/frontend/editor/src/core/assets/brand/classic-logo/StirlingPDFLogoWhiteText.svg similarity index 100% rename from frontend/shared/assets/brand/classic-logo/StirlingPDFLogoWhiteText.svg rename to frontend/editor/src/core/assets/brand/classic-logo/StirlingPDFLogoWhiteText.svg diff --git a/frontend/shared/assets/brand/classic-logo/favicon.ico b/frontend/editor/src/core/assets/brand/classic-logo/favicon.ico similarity index 100% rename from frontend/shared/assets/brand/classic-logo/favicon.ico rename to frontend/editor/src/core/assets/brand/classic-logo/favicon.ico diff --git a/frontend/shared/assets/brand/classic-logo/logo-tooltip.svg b/frontend/editor/src/core/assets/brand/classic-logo/logo-tooltip.svg similarity index 100% rename from frontend/shared/assets/brand/classic-logo/logo-tooltip.svg rename to frontend/editor/src/core/assets/brand/classic-logo/logo-tooltip.svg diff --git a/frontend/shared/assets/brand/classic-logo/logo192.png b/frontend/editor/src/core/assets/brand/classic-logo/logo192.png similarity index 100% rename from frontend/shared/assets/brand/classic-logo/logo192.png rename to frontend/editor/src/core/assets/brand/classic-logo/logo192.png diff --git a/frontend/shared/assets/brand/classic-logo/logo512.png b/frontend/editor/src/core/assets/brand/classic-logo/logo512.png similarity index 100% rename from frontend/shared/assets/brand/classic-logo/logo512.png rename to frontend/editor/src/core/assets/brand/classic-logo/logo512.png diff --git a/frontend/shared/assets/brand/modern-logo/Firstpage.png b/frontend/editor/src/core/assets/brand/modern-logo/Firstpage.png similarity index 100% rename from frontend/shared/assets/brand/modern-logo/Firstpage.png rename to frontend/editor/src/core/assets/brand/modern-logo/Firstpage.png diff --git a/frontend/shared/assets/brand/modern-logo/LoginDarkModeHeader.svg b/frontend/editor/src/core/assets/brand/modern-logo/LoginDarkModeHeader.svg similarity index 100% rename from frontend/shared/assets/brand/modern-logo/LoginDarkModeHeader.svg rename to frontend/editor/src/core/assets/brand/modern-logo/LoginDarkModeHeader.svg diff --git a/frontend/shared/assets/brand/modern-logo/LoginLightModeHeader.svg b/frontend/editor/src/core/assets/brand/modern-logo/LoginLightModeHeader.svg similarity index 100% rename from frontend/shared/assets/brand/modern-logo/LoginLightModeHeader.svg rename to frontend/editor/src/core/assets/brand/modern-logo/LoginLightModeHeader.svg diff --git a/frontend/shared/assets/brand/modern-logo/StirlingPDFLogoBlackText.svg b/frontend/editor/src/core/assets/brand/modern-logo/StirlingPDFLogoBlackText.svg similarity index 100% rename from frontend/shared/assets/brand/modern-logo/StirlingPDFLogoBlackText.svg rename to frontend/editor/src/core/assets/brand/modern-logo/StirlingPDFLogoBlackText.svg diff --git a/frontend/shared/assets/brand/modern-logo/StirlingPDFLogoGreyText.svg b/frontend/editor/src/core/assets/brand/modern-logo/StirlingPDFLogoGreyText.svg similarity index 100% rename from frontend/shared/assets/brand/modern-logo/StirlingPDFLogoGreyText.svg rename to frontend/editor/src/core/assets/brand/modern-logo/StirlingPDFLogoGreyText.svg diff --git a/frontend/shared/assets/brand/modern-logo/StirlingPDFLogoNoTextDark.svg b/frontend/editor/src/core/assets/brand/modern-logo/StirlingPDFLogoNoTextDark.svg similarity index 100% rename from frontend/shared/assets/brand/modern-logo/StirlingPDFLogoNoTextDark.svg rename to frontend/editor/src/core/assets/brand/modern-logo/StirlingPDFLogoNoTextDark.svg diff --git a/frontend/shared/assets/brand/modern-logo/StirlingPDFLogoNoTextLight.svg b/frontend/editor/src/core/assets/brand/modern-logo/StirlingPDFLogoNoTextLight.svg similarity index 100% rename from frontend/shared/assets/brand/modern-logo/StirlingPDFLogoNoTextLight.svg rename to frontend/editor/src/core/assets/brand/modern-logo/StirlingPDFLogoNoTextLight.svg diff --git a/frontend/shared/assets/brand/modern-logo/StirlingPDFLogoWhiteText.svg b/frontend/editor/src/core/assets/brand/modern-logo/StirlingPDFLogoWhiteText.svg similarity index 100% rename from frontend/shared/assets/brand/modern-logo/StirlingPDFLogoWhiteText.svg rename to frontend/editor/src/core/assets/brand/modern-logo/StirlingPDFLogoWhiteText.svg diff --git a/frontend/portal/public/favicon.ico b/frontend/editor/src/core/assets/brand/modern-logo/favicon.ico similarity index 100% rename from frontend/portal/public/favicon.ico rename to frontend/editor/src/core/assets/brand/modern-logo/favicon.ico diff --git a/frontend/shared/assets/brand/modern-logo/logo-tooltip.svg b/frontend/editor/src/core/assets/brand/modern-logo/logo-tooltip.svg similarity index 100% rename from frontend/shared/assets/brand/modern-logo/logo-tooltip.svg rename to frontend/editor/src/core/assets/brand/modern-logo/logo-tooltip.svg diff --git a/frontend/shared/assets/brand/modern-logo/logo192.png b/frontend/editor/src/core/assets/brand/modern-logo/logo192.png similarity index 100% rename from frontend/shared/assets/brand/modern-logo/logo192.png rename to frontend/editor/src/core/assets/brand/modern-logo/logo192.png diff --git a/frontend/shared/assets/brand/modern-logo/logo512.png b/frontend/editor/src/core/assets/brand/modern-logo/logo512.png similarity index 100% rename from frontend/shared/assets/brand/modern-logo/logo512.png rename to frontend/editor/src/core/assets/brand/modern-logo/logo512.png diff --git a/frontend/shared/assets/login/AddToPDF.png b/frontend/editor/src/core/assets/login/AddToPDF.png similarity index 100% rename from frontend/shared/assets/login/AddToPDF.png rename to frontend/editor/src/core/assets/login/AddToPDF.png diff --git a/frontend/shared/assets/login/Firstpage.png b/frontend/editor/src/core/assets/login/Firstpage.png similarity index 100% rename from frontend/shared/assets/login/Firstpage.png rename to frontend/editor/src/core/assets/login/Firstpage.png diff --git a/frontend/shared/assets/login/LoginBackgroundPanel.png b/frontend/editor/src/core/assets/login/LoginBackgroundPanel.png similarity index 100% rename from frontend/shared/assets/login/LoginBackgroundPanel.png rename to frontend/editor/src/core/assets/login/LoginBackgroundPanel.png diff --git a/frontend/shared/assets/login/SecurePDF.png b/frontend/editor/src/core/assets/login/SecurePDF.png similarity index 100% rename from frontend/shared/assets/login/SecurePDF.png rename to frontend/editor/src/core/assets/login/SecurePDF.png diff --git a/frontend/shared/assets/login/apple.svg b/frontend/editor/src/core/assets/login/apple.svg similarity index 100% rename from frontend/shared/assets/login/apple.svg rename to frontend/editor/src/core/assets/login/apple.svg diff --git a/frontend/shared/assets/login/authentik.svg b/frontend/editor/src/core/assets/login/authentik.svg similarity index 100% rename from frontend/shared/assets/login/authentik.svg rename to frontend/editor/src/core/assets/login/authentik.svg diff --git a/frontend/shared/assets/login/cloudron.svg b/frontend/editor/src/core/assets/login/cloudron.svg similarity index 100% rename from frontend/shared/assets/login/cloudron.svg rename to frontend/editor/src/core/assets/login/cloudron.svg diff --git a/frontend/shared/assets/login/github.svg b/frontend/editor/src/core/assets/login/github.svg similarity index 100% rename from frontend/shared/assets/login/github.svg rename to frontend/editor/src/core/assets/login/github.svg diff --git a/frontend/shared/assets/login/google.svg b/frontend/editor/src/core/assets/login/google.svg similarity index 100% rename from frontend/shared/assets/login/google.svg rename to frontend/editor/src/core/assets/login/google.svg diff --git a/frontend/shared/assets/login/keycloak.svg b/frontend/editor/src/core/assets/login/keycloak.svg similarity index 100% rename from frontend/shared/assets/login/keycloak.svg rename to frontend/editor/src/core/assets/login/keycloak.svg diff --git a/frontend/shared/assets/login/microsoft.svg b/frontend/editor/src/core/assets/login/microsoft.svg similarity index 100% rename from frontend/shared/assets/login/microsoft.svg rename to frontend/editor/src/core/assets/login/microsoft.svg diff --git a/frontend/shared/assets/login/oidc.svg b/frontend/editor/src/core/assets/login/oidc.svg similarity index 100% rename from frontend/shared/assets/login/oidc.svg rename to frontend/editor/src/core/assets/login/oidc.svg diff --git a/frontend/shared/auth/ui/oauthIcons.ts b/frontend/editor/src/core/auth/ui/oauthIcons.ts similarity index 67% rename from frontend/shared/auth/ui/oauthIcons.ts rename to frontend/editor/src/core/auth/ui/oauthIcons.ts index c588274b92..f0d879786f 100644 --- a/frontend/shared/auth/ui/oauthIcons.ts +++ b/frontend/editor/src/core/auth/ui/oauthIcons.ts @@ -6,14 +6,14 @@ * editor's saas/desktop login buttons, and the config provider list) share one * copy that works in both the editor and the portal bundles. */ -import googleIcon from "@shared/assets/login/google.svg"; -import githubIcon from "@shared/assets/login/github.svg"; -import appleIcon from "@shared/assets/login/apple.svg"; -import microsoftIcon from "@shared/assets/login/microsoft.svg"; -import keycloakIcon from "@shared/assets/login/keycloak.svg"; -import cloudronIcon from "@shared/assets/login/cloudron.svg"; -import authentikIcon from "@shared/assets/login/authentik.svg"; -import oidcIcon from "@shared/assets/login/oidc.svg"; +import googleIcon from "@app/assets/login/google.svg"; +import githubIcon from "@app/assets/login/github.svg"; +import appleIcon from "@app/assets/login/apple.svg"; +import microsoftIcon from "@app/assets/login/microsoft.svg"; +import keycloakIcon from "@app/assets/login/keycloak.svg"; +import cloudronIcon from "@app/assets/login/cloudron.svg"; +import authentikIcon from "@app/assets/login/authentik.svg"; +import oidcIcon from "@app/assets/login/oidc.svg"; /** Generic fallback icon (filename) for unknown providers. */ export const GENERIC_PROVIDER_ICON = "oidc.svg"; diff --git a/frontend/editor/src/core/components/shared/ThemeProvider.tsx b/frontend/editor/src/core/components/shared/ThemeProvider.tsx index 2dda9b8d39..bf15ce187f 100644 --- a/frontend/editor/src/core/components/shared/ThemeProvider.tsx +++ b/frontend/editor/src/core/components/shared/ThemeProvider.tsx @@ -18,8 +18,8 @@ import { getSystemTheme, resolveColorScheme, } from "@app/constants/theme"; -// SUI shared design-system tokens (used by @shared/components); key on `data-theme`. -import "@shared/tokens/tokens.css"; +// SUI shared design-system tokens (used by @app/ui); key on `data-theme`. +import "@app/tokens/tokens.css"; interface ThemeContextType { themeMode: ThemeMode; diff --git a/frontend/editor/src/core/components/shared/ToolPanelHeader.css b/frontend/editor/src/core/components/shared/ToolPanelHeader.css new file mode 100644 index 0000000000..f1d99d1913 --- /dev/null +++ b/frontend/editor/src/core/components/shared/ToolPanelHeader.css @@ -0,0 +1,93 @@ +/* ===================== PanelHeader (core) ===================== */ +/* Header for the active-tool rail. Core-owned so the OSS build has no */ +/* design-system dependency; mirrors the shared rail header treatment so it */ +/* reads well in both light and dark mode (thin border, no heavy fill). */ + +.sui-panelhdr { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 1rem 1rem 0.75rem; + flex-shrink: 0; +} + +.sui-panelhdr__bar { + display: inline-flex; + align-items: center; + gap: 0.5rem; + flex: 1; + min-width: 0; + padding: 0.4rem 0.75rem 0.4rem 0.4rem; + border: 1px solid var(--border-subtle, var(--mantine-color-default-border)); + border-radius: 9999px; + background: var(--mantine-color-body); + text-align: left; + color: inherit; +} + +.sui-panelhdr__icon { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.75rem; + height: 1.75rem; + border-radius: 9999px; + background: var(--mantine-color-blue-light); + color: var(--mantine-color-blue-filled); + flex-shrink: 0; +} + +.sui-panelhdr__icon svg { + font-size: 1rem; + width: 1rem; + height: 1rem; +} + +/* ToolIcon wraps its glyph in .tool-button-icon with its own margin/transform; + reset them so the glyph sits dead-centre in the circular badge. */ +.sui-panelhdr__icon .tool-button-icon { + margin: 0 !important; + transform: none !important; + display: inline-flex; + align-items: center; + justify-content: center; + line-height: 1; +} + +.sui-panelhdr__label { + flex: 1; + min-width: 0; + font-size: 0.9rem; + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: var(--mantine-color-text); +} + +/* Dark mode: let the header blend into the rail — just a thin border, no fill — + so it doesn't read as a clashing lighter card on the dark toolbar. */ +[data-mantine-color-scheme="dark"] .sui-panelhdr__bar { + background: transparent; + border-color: var(--border-subtle, var(--mantine-color-default-border)); +} + +[data-mantine-color-scheme="dark"] .sui-panelhdr__icon { + background: color-mix( + in srgb, + var(--mantine-color-blue-filled) 18%, + transparent + ); + color: var(--mantine-color-blue-3, var(--mantine-color-blue-filled)); +} + +/* Dark mode: the subtle gray close button is too dim against the dark rail — + brighten it to a clearly-visible light grey (near-white on hover). */ +[data-mantine-color-scheme="dark"] .sui-panelhdr__close { + color: var(--mantine-color-gray-4); +} + +[data-mantine-color-scheme="dark"] .sui-panelhdr__close:hover { + color: var(--mantine-color-gray-2); +} diff --git a/frontend/editor/src/core/components/shared/ToolPanelHeader.tsx b/frontend/editor/src/core/components/shared/ToolPanelHeader.tsx new file mode 100644 index 0000000000..e34ca4c6c2 --- /dev/null +++ b/frontend/editor/src/core/components/shared/ToolPanelHeader.tsx @@ -0,0 +1,53 @@ +import type { ReactNode } from "react"; +import { ActionIcon } from "@mantine/core"; +import CloseIcon from "@mui/icons-material/Close"; +import "@app/components/shared/ToolPanelHeader.css"; + +export interface ToolPanelHeaderProps { + /** Glyph rendered in the tinted circular badge at the header's leading edge. */ + icon: ReactNode; + /** Header title. */ + title: ReactNode; + /** Close (X) handler. The trailing close button renders only when supplied. */ + onClose?: () => void; + /** aria-label for the close button. */ + closeLabel?: string; +} + +/** + * Header for the active-tool rail surface: a tinted icon badge + title in a + * rounded bar with a trailing close button. + * + * Core-owned so the OSS build carries no design-system dependency. It mirrors + * the shared rail header's styling for the simple (non-menu) case the tool panel + * needs; the richer variant (dropdown menu, accents, loading dot) lives in the + * design system for the AI chat and Policies surfaces. + */ +export function ToolPanelHeader({ + icon, + title, + onClose, + closeLabel, +}: ToolPanelHeaderProps) { + return ( +
+
+ {icon} + {title} +
+ {onClose && ( + + + + )} +
+ ); +} diff --git a/frontend/editor/src/core/components/shared/config/configSections/providerDefinitions.ts b/frontend/editor/src/core/components/shared/config/configSections/providerDefinitions.ts index 6b5983822f..72dc675f8f 100644 --- a/frontend/editor/src/core/components/shared/config/configSections/providerDefinitions.ts +++ b/frontend/editor/src/core/components/shared/config/configSections/providerDefinitions.ts @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { oauthIconUrl } from "@shared/auth/ui/oauthIcons"; +import { oauthIconUrl } from "@app/auth/ui/oauthIcons"; export type ProviderType = "oauth2" | "saml2" | "telegram" | "googledrive"; diff --git a/frontend/editor/src/core/components/tools/RightSidebar.tsx b/frontend/editor/src/core/components/tools/RightSidebar.tsx index bfd98d7b32..7e3aa04629 100644 --- a/frontend/editor/src/core/components/tools/RightSidebar.tsx +++ b/frontend/editor/src/core/components/tools/RightSidebar.tsx @@ -19,7 +19,7 @@ import { useFavoriteToolItems } from "@app/hooks/tools/useFavoriteToolItems"; import { useToolSections } from "@app/hooks/useToolSections"; import type { SubcategoryGroup } from "@app/hooks/useToolSections"; import { ToolIcon } from "@app/components/shared/ToolIcon"; -import { PanelHeader } from "@shared/components/PanelHeader"; +import { ToolPanelHeader } from "@app/components/shared/ToolPanelHeader"; import { Tooltip as AppTooltip } from "@app/components/shared/Tooltip"; import { withViewTransition } from "@app/utils/viewTransition"; import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; @@ -291,7 +291,7 @@ export default function RightSidebar() { <> {!showPolicies && (activeTool ? ( - { describe("env vars", () => { it("every VITE_ var used in source is present in an example env file", () => { const baseEnv = readFileSync(join(frontendRoot, ".env"), "utf-8"); + const proprietaryEnv = readFileSync( + join(frontendRoot, ".env.proprietary"), + "utf-8", + ); const desktopEnv = readFileSync( join(frontendRoot, ".env.desktop"), "utf-8", @@ -58,6 +62,7 @@ describe("env vars", () => { const declaredKeys = new Set([ ...parseEnvKeys(baseEnv), + ...parseEnvKeys(proprietaryEnv), ...parseEnvKeys(desktopEnv), ...parseEnvKeys(saasEnv), ]); diff --git a/frontend/editor/src/core/hooks/useLogoAssets.test.ts b/frontend/editor/src/core/hooks/useLogoAssets.test.ts index b22701dedd..3d83fb35f4 100644 --- a/frontend/editor/src/core/hooks/useLogoAssets.test.ts +++ b/frontend/editor/src/core/hooks/useLogoAssets.test.ts @@ -10,11 +10,11 @@ import type { LogoVariant } from "@app/services/preferencesService"; */ describe("useLogoAssets - Logo Asset Files", () => { const publicDir = path.resolve(__dirname, "../../../public"); - // Brand logo assets live in the shared design system; the editor's vite - // config copies shared/assets/brand//* into the served root at build - // time (see viteStaticCopy in editor/vite.config.ts), so useLogoAssets can - // keep referencing them by their public-URL path. Validate them at source. - const brandDir = path.resolve(__dirname, "../../../../shared/assets/brand"); + // Brand logo assets live in core; the editor's vite config copies + // core/assets/brand//* into the served root at build time (see + // viteStaticCopy in editor/vite.config.ts), so useLogoAssets can keep + // referencing them by their public-URL path. Validate them at source. + const brandDir = path.resolve(__dirname, "../assets/brand"); // All asset files that useLogoAssets references const requiredAssets = [ diff --git a/frontend/editor/src/core/i18n.ts b/frontend/editor/src/core/i18n.ts index 82b860b467..9663a1c2e2 100644 --- a/frontend/editor/src/core/i18n.ts +++ b/frontend/editor/src/core/i18n.ts @@ -1,7 +1,7 @@ import i18n from "i18next"; import { initReactI18next } from "react-i18next"; import LanguageDetector from "i18next-browser-languagedetector"; -import TomlBackend from "@shared/i18n/tomlBackend"; +import TomlBackend from "@app/i18n/tomlBackend"; import { supportedLanguages, rtlLanguages, @@ -10,10 +10,10 @@ import { normalizeLanguageCode, toUnderscoreFormat, toUnderscoreLanguages, -} from "@shared/i18n/languages"; +} from "@app/i18n/languages"; // Language metadata and code helpers are shared with the portal via -// @shared/i18n. Re-export them so existing `@app/i18n` consumers are unchanged. +// @app/i18n. Re-export them so existing `@app/i18n` consumers are unchanged. export { supportedLanguages, rtlLanguages, diff --git a/frontend/editor/src/core/i18n/config.ts b/frontend/editor/src/core/i18n/config.ts index 47e028c1f4..02b79f06b1 100644 --- a/frontend/editor/src/core/i18n/config.ts +++ b/frontend/editor/src/core/i18n/config.ts @@ -1,6 +1,6 @@ import i18n from "i18next"; import { initReactI18next } from "react-i18next"; -import TomlBackend from "@shared/i18n/tomlBackend"; +import TomlBackend from "@app/i18n/tomlBackend"; i18n .use(TomlBackend) diff --git a/frontend/shared/i18n/languages.ts b/frontend/editor/src/core/i18n/languages.ts similarity index 100% rename from frontend/shared/i18n/languages.ts rename to frontend/editor/src/core/i18n/languages.ts diff --git a/frontend/shared/i18n/tomlBackend.ts b/frontend/editor/src/core/i18n/tomlBackend.ts similarity index 100% rename from frontend/shared/i18n/tomlBackend.ts rename to frontend/editor/src/core/i18n/tomlBackend.ts diff --git a/frontend/shared/i18n/translationAudit.ts b/frontend/editor/src/core/i18n/translationAudit.ts similarity index 90% rename from frontend/shared/i18n/translationAudit.ts rename to frontend/editor/src/core/i18n/translationAudit.ts index 50cd1b2c76..e2cc309c72 100644 --- a/frontend/shared/i18n/translationAudit.ts +++ b/frontend/editor/src/core/i18n/translationAudit.ts @@ -19,8 +19,8 @@ import { fileURLToPath } from "url"; import ts from "typescript"; import { parse } from "smol-toml"; -const HERE = path.dirname(fileURLToPath(import.meta.url)); // frontend/shared/i18n -const FRONTEND_ROOT = path.resolve(HERE, "../.."); // frontend +const HERE = path.dirname(fileURLToPath(import.meta.url)); // frontend/editor/src/core/i18n +const FRONTEND_ROOT = path.resolve(HERE, "../../../.."); // frontend /** Repo root, for rendering source paths relative in CI annotations. */ export const REPO_ROOT = path.resolve(FRONTEND_ROOT, ".."); @@ -29,12 +29,6 @@ export interface TranslationProject { name: string; /** Absolute path to the source tree to scan. */ srcRoot: string; - /** - * Extra source trees that also contribute used keys (e.g. the shared layer - * (frontend/shared), whose components (login UI, etc.) are rendered by both - * apps and reference each app's locale keys). - */ - extraRoots?: string[]; /** Absolute path to the en-US source locale. */ localeFile: string; /** Static keys flagged as missing that are genuinely fine (false positives). */ @@ -68,11 +62,6 @@ const PLURAL_SUFFIX_RE = /_(zero|one|two|few|many|other)$/; const front = (rel: string): string => path.join(FRONTEND_ROOT, rel); -// The shared layer is rendered by both apps, so its translation usage counts -// toward each project (or keys used only from shared look unused, and a shared -// component's keys go unvalidated against an app's locale). -const SHARED_SRC = front("shared"); - /** * The projects the i18n suites guard. Each carries its own ignore lists: the * editor exempts a few runtime-assembled key families; the portal starts clean. @@ -80,8 +69,11 @@ const SHARED_SRC = front("shared"); export const I18N_PROJECTS: TranslationProject[] = [ { name: "editor", + // One project over the whole editor tree, including the portal layer. Portal + // strings live under portal.* in the shared locale and are referenced with + // that prefix in source (t("portal.users.title")), so they validate here + // like any other key. srcRoot: front("editor/src"), - extraRoots: [SHARED_SRC], localeFile: front("editor/public/locales/en-US/translation.toml"), ignoredKeyPatterns: [ // SignSettings / SavedSignaturesSection resolve every key as @@ -93,24 +85,14 @@ export const I18N_PROJECTS: TranslationProject[] = [ /^account\./, // [language] direction is read by the i18n layer, never as a UI string. /^language\.direction$/, + // Portal source-type copy is referenced via metadata keys in + // components/sources/sourceTypes.ts (t(field.labelKey)), invisible to the + // static scan. + /^portal\.sources\.types\./, ], minUsedKeys: 100, minLocaleKeys: 100, }, - { - name: "portal", - srcRoot: front("portal/src"), - extraRoots: [SHARED_SRC], - localeFile: front("portal/public/locales/en-US/translation.toml"), - ignoredKeyPatterns: [ - // Source-type copy is referenced via metadata keys in - // components/sources/sourceTypes.ts (t(field.labelKey)), so the static - // scan can't see these as used. - /^sources\.types\./, - ], - minUsedKeys: 20, - minLocaleKeys: 20, - }, ]; // Locale tables are shallow by design; the cap only trips on a pathological @@ -189,11 +171,6 @@ const listSourceFiles = (roots: string[]): string[] => ) .filter((file) => !IGNORED_FILE_PATTERNS.some((re) => re.test(file))); -const projectRoots = (project: TranslationProject): string[] => [ - project.srcRoot, - ...(project.extraRoots ?? []), -]; - const hasPluralCoverage = (key: string, available: Set): boolean => [...available].some( (k) => k.startsWith(`${key}_`) && PLURAL_SUFFIX_RE.test(k), @@ -347,7 +324,7 @@ export function findMissingKeys(project: TranslationProject): { return false; }; - const used = listSourceFiles(projectRoots(project)) + const used = listSourceFiles([project.srcRoot]) .flatMap(extractStaticKeys) .filter(({ key }) => !ignored.has(key)); const missing = used.filter(({ key }) => !resolves(key)); @@ -360,7 +337,7 @@ export function findUnusedKeys(project: TranslationProject): { localeCount: number; } { const localeKeys = Array.from(collectLocaleKeys(project.localeFile)); - const files = listSourceFiles(projectRoots(project)); + const files = listSourceFiles([project.srcRoot]); const source = files.map((file) => fs.readFileSync(file, "utf8")).join("\n"); const shapes = new Set(); diff --git a/frontend/editor/src/core/routes/adminRouteExtensions.tsx b/frontend/editor/src/core/routes/adminRouteExtensions.tsx new file mode 100644 index 0000000000..657d4799a3 --- /dev/null +++ b/frontend/editor/src/core/routes/adminRouteExtensions.tsx @@ -0,0 +1,10 @@ +import type { ReactElement } from "react"; + +/** + * Admin-only route-set contributed by higher layers (the portal). The OSS core + * build ships none, so this stub returns an empty list and the portal chunk is + * never referenced in the core bundle. + */ +export function getAdminRouteExtensions(): ReactElement[] { + return []; +} diff --git a/frontend/editor/src/core/tests/missingTranslations.test.ts b/frontend/editor/src/core/tests/missingTranslations.test.ts index f98022559b..617699e43a 100644 --- a/frontend/editor/src/core/tests/missingTranslations.test.ts +++ b/frontend/editor/src/core/tests/missingTranslations.test.ts @@ -5,10 +5,10 @@ import { I18N_PROJECTS, REPO_ROOT, findMissingKeys, -} from "@shared/i18n/translationAudit"; +} from "@app/i18n/translationAudit"; // One suite per frontend app (editor + portal). The scan logic lives in -// @shared/i18n/translationAudit so both apps share one implementation. +// @app/i18n/translationAudit so both apps share one implementation. describe.each(I18N_PROJECTS)( "Missing translation coverage — $name", (project) => { diff --git a/frontend/editor/src/core/tests/unusedTranslations.test.ts b/frontend/editor/src/core/tests/unusedTranslations.test.ts index 1193c737bf..f0d0fbce93 100644 --- a/frontend/editor/src/core/tests/unusedTranslations.test.ts +++ b/frontend/editor/src/core/tests/unusedTranslations.test.ts @@ -5,10 +5,10 @@ import { I18N_PROJECTS, REPO_ROOT, findUnusedKeys, -} from "@shared/i18n/translationAudit"; +} from "@app/i18n/translationAudit"; // One suite per frontend app (editor + portal). The scan logic lives in -// @shared/i18n/translationAudit so both apps share one implementation; each +// @app/i18n/translationAudit so both apps share one implementation; each // project carries its own ignoredKeyPatterns for runtime-assembled keys. describe.each(I18N_PROJECTS)( "Unused translation coverage — $name", @@ -38,7 +38,7 @@ describe.each(I18N_PROJECTS)( `Found ${unused.length} unused en-US translation key(s). ` + `Remove them from ${localeRelative}, or (if the usage is too ` + `dynamic for the heuristic to spot) add a pattern to this ` + - `project's ignoredKeyPatterns in @shared/i18n/translationAudit.`, + `project's ignoredKeyPatterns in @app/i18n/translationAudit.`, ).toEqual([]); }, ); diff --git a/frontend/shared/tokens/Tokens.stories.tsx b/frontend/editor/src/core/tokens/Tokens.stories.tsx similarity index 100% rename from frontend/shared/tokens/Tokens.stories.tsx rename to frontend/editor/src/core/tokens/Tokens.stories.tsx diff --git a/frontend/shared/tokens/base.css b/frontend/editor/src/core/tokens/base.css similarity index 100% rename from frontend/shared/tokens/base.css rename to frontend/editor/src/core/tokens/base.css diff --git a/frontend/shared/tokens/tokens.css b/frontend/editor/src/core/tokens/tokens.css similarity index 100% rename from frontend/shared/tokens/tokens.css rename to frontend/editor/src/core/tokens/tokens.css diff --git a/frontend/shared/tokens/tokens.ts b/frontend/editor/src/core/tokens/tokens.ts similarity index 100% rename from frontend/shared/tokens/tokens.ts rename to frontend/editor/src/core/tokens/tokens.ts diff --git a/frontend/editor/src/core/tsconfig.json b/frontend/editor/src/core/tsconfig.json index 781d894b6d..08bf588c6a 100644 --- a/frontend/editor/src/core/tsconfig.json +++ b/frontend/editor/src/core/tsconfig.json @@ -2,8 +2,7 @@ "extends": "../../tsconfig.json", "compilerOptions": { "paths": { - "@app/*": ["../../src/core/*"], - "@shared/*": ["../../../shared/*"] + "@app/*": ["../../src/core/*"] } }, "include": ["../global.d.ts", "../*.js", "../*.ts", "../*.tsx", "."] diff --git a/frontend/editor/src/desktop/components/SetupWizard/DesktopAuthLayout.tsx b/frontend/editor/src/desktop/components/SetupWizard/DesktopAuthLayout.tsx index ad783e9f76..f5e64f2927 100644 --- a/frontend/editor/src/desktop/components/SetupWizard/DesktopAuthLayout.tsx +++ b/frontend/editor/src/desktop/components/SetupWizard/DesktopAuthLayout.tsx @@ -1,8 +1,8 @@ import React, { useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import LoginRightCarousel from "@shared/auth/ui/LoginRightCarousel"; +import LoginRightCarousel from "@app/auth/ui/LoginRightCarousel"; import buildLoginSlides from "@app/components/shared/loginSlides"; -import styles from "@shared/auth/ui/AuthShell.module.css"; +import styles from "@app/auth/ui/AuthShell.module.css"; import { useLogoVariant } from "@app/hooks/useLogoVariant"; interface DesktopAuthLayoutProps { diff --git a/frontend/editor/src/desktop/components/SetupWizard/DesktopOAuthButtons.tsx b/frontend/editor/src/desktop/components/SetupWizard/DesktopOAuthButtons.tsx index c2da65845b..c9813d818e 100644 --- a/frontend/editor/src/desktop/components/SetupWizard/DesktopOAuthButtons.tsx +++ b/frontend/editor/src/desktop/components/SetupWizard/DesktopOAuthButtons.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { authService, UserInfo } from "@app/services/authService"; import { buildOAuthCallbackHtml } from "@app/utils/oauthCallbackHtml"; -import { oauthIconUrl } from "@shared/auth/ui/oauthIcons"; +import { oauthIconUrl } from "@app/auth/ui/oauthIcons"; import { STIRLING_SAAS_URL } from "@app/constants/connection"; import "@app/components/SetupWizard/desktopOAuth.css"; diff --git a/frontend/editor/src/desktop/components/SetupWizard/SaaSLoginScreen.tsx b/frontend/editor/src/desktop/components/SetupWizard/SaaSLoginScreen.tsx index 7f679052a3..dc346ef9ac 100644 --- a/frontend/editor/src/desktop/components/SetupWizard/SaaSLoginScreen.tsx +++ b/frontend/editor/src/desktop/components/SetupWizard/SaaSLoginScreen.tsx @@ -1,13 +1,13 @@ import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import LoginHeader from "@app/routes/login/LoginHeader"; -import ErrorMessage from "@shared/auth/ui/ErrorMessage"; -import EmailPasswordForm from "@shared/auth/ui/EmailPasswordForm"; +import ErrorMessage from "@app/auth/ui/ErrorMessage"; +import EmailPasswordForm from "@app/auth/ui/EmailPasswordForm"; import DividerWithText from "@app/components/shared/DividerWithText"; import { DesktopOAuthButtons } from "@app/components/SetupWizard/DesktopOAuthButtons"; import { SelfHostedLink } from "@app/components/SetupWizard/SelfHostedLink"; import { UserInfo } from "@app/services/authService"; -import "@shared/auth/ui/auth.css"; +import "@app/auth/ui/auth.css"; interface SaaSLoginScreenProps { serverUrl: string; diff --git a/frontend/editor/src/desktop/components/SetupWizard/SaaSSignupScreen.tsx b/frontend/editor/src/desktop/components/SetupWizard/SaaSSignupScreen.tsx index bcc7bf88c7..a3765c2c98 100644 --- a/frontend/editor/src/desktop/components/SetupWizard/SaaSSignupScreen.tsx +++ b/frontend/editor/src/desktop/components/SetupWizard/SaaSSignupScreen.tsx @@ -1,14 +1,14 @@ import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import LoginHeader from "@app/routes/login/LoginHeader"; -import ErrorMessage from "@shared/auth/ui/ErrorMessage"; +import ErrorMessage from "@app/auth/ui/ErrorMessage"; import SignupForm from "@app/routes/signup/SignupForm"; import { useSignupFormValidation, SignupFieldErrors, } from "@app/routes/signup/SignupFormValidation"; import { authService } from "@app/services/authService"; -import "@shared/auth/ui/auth.css"; +import "@app/auth/ui/auth.css"; interface SaaSSignupScreenProps { loading: boolean; diff --git a/frontend/editor/src/desktop/components/SetupWizard/SelfHostedLink.tsx b/frontend/editor/src/desktop/components/SetupWizard/SelfHostedLink.tsx index 739c02516d..8d5c98fe6e 100644 --- a/frontend/editor/src/desktop/components/SetupWizard/SelfHostedLink.tsx +++ b/frontend/editor/src/desktop/components/SetupWizard/SelfHostedLink.tsx @@ -1,6 +1,6 @@ import React from "react"; import { useTranslation } from "react-i18next"; -import "@shared/auth/ui/auth.css"; +import "@app/auth/ui/auth.css"; interface SelfHostedLinkProps { onClick: () => void; diff --git a/frontend/editor/src/desktop/components/SetupWizard/SelfHostedLoginScreen.tsx b/frontend/editor/src/desktop/components/SetupWizard/SelfHostedLoginScreen.tsx index ff2d743809..34a26a8fa0 100644 --- a/frontend/editor/src/desktop/components/SetupWizard/SelfHostedLoginScreen.tsx +++ b/frontend/editor/src/desktop/components/SetupWizard/SelfHostedLoginScreen.tsx @@ -2,13 +2,13 @@ import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { Text } from "@mantine/core"; import LoginHeader from "@app/routes/login/LoginHeader"; -import ErrorMessage from "@shared/auth/ui/ErrorMessage"; -import EmailPasswordForm from "@shared/auth/ui/EmailPasswordForm"; +import ErrorMessage from "@app/auth/ui/ErrorMessage"; +import EmailPasswordForm from "@app/auth/ui/EmailPasswordForm"; import DividerWithText from "@app/components/shared/DividerWithText"; import { DesktopOAuthButtons } from "@app/components/SetupWizard/DesktopOAuthButtons"; import { UserInfo } from "@app/services/authService"; import { SSOProviderConfig } from "@app/services/connectionModeService"; -import "@shared/auth/ui/auth.css"; +import "@app/auth/ui/auth.css"; interface SelfHostedLoginScreenProps { serverUrl: string; diff --git a/frontend/editor/src/desktop/components/SetupWizard/ServerSelectionScreen.tsx b/frontend/editor/src/desktop/components/SetupWizard/ServerSelectionScreen.tsx index 7ddf7b3cb2..c3fc2f3846 100644 --- a/frontend/editor/src/desktop/components/SetupWizard/ServerSelectionScreen.tsx +++ b/frontend/editor/src/desktop/components/SetupWizard/ServerSelectionScreen.tsx @@ -1,10 +1,10 @@ import React from "react"; import { useTranslation } from "react-i18next"; import LoginHeader from "@app/routes/login/LoginHeader"; -import ErrorMessage from "@shared/auth/ui/ErrorMessage"; +import ErrorMessage from "@app/auth/ui/ErrorMessage"; import { ServerSelection } from "@app/components/SetupWizard/ServerSelection"; import { ServerConfig } from "@app/services/connectionModeService"; -import "@shared/auth/ui/auth.css"; +import "@app/auth/ui/auth.css"; interface ServerSelectionScreenProps { onSelect: (config: ServerConfig) => void; diff --git a/frontend/editor/src/desktop/components/SetupWizard/index.tsx b/frontend/editor/src/desktop/components/SetupWizard/index.tsx index e218f06e0c..2f6ddaba2b 100644 --- a/frontend/editor/src/desktop/components/SetupWizard/index.tsx +++ b/frontend/editor/src/desktop/components/SetupWizard/index.tsx @@ -19,7 +19,7 @@ import { import { tauriBackendService } from "@app/services/tauriBackendService"; import { STIRLING_SAAS_URL } from "@app/constants/connection"; import { listen } from "@tauri-apps/api/event"; -import "@shared/auth/ui/auth.css"; +import "@app/auth/ui/auth.css"; import { DisabledButtonWithTooltip } from "@app/components/shared/DisabledButtonWithTooltip"; enum SetupStep { diff --git a/frontend/editor/src/desktop/routes/adminRouteExtensions.tsx b/frontend/editor/src/desktop/routes/adminRouteExtensions.tsx new file mode 100644 index 0000000000..afd92050bc --- /dev/null +++ b/frontend/editor/src/desktop/routes/adminRouteExtensions.tsx @@ -0,0 +1,10 @@ +import type { ReactElement } from "react"; + +/** + * Desktop inherits proprietary's App but must NOT ship the portal. Shadowing + * the seam back to empty means the desktop bundle never references PortalApp, + * so the portal chunk is not emitted (and @portal never has to resolve there). + */ +export function getAdminRouteExtensions(): ReactElement[] { + return []; +} diff --git a/frontend/editor/src/desktop/tsconfig.json b/frontend/editor/src/desktop/tsconfig.json index 27f094be66..e7f83893c4 100644 --- a/frontend/editor/src/desktop/tsconfig.json +++ b/frontend/editor/src/desktop/tsconfig.json @@ -10,8 +10,7 @@ ], "@cloud/*": ["../../src/cloud/*"], "@proprietary/*": ["../../src/proprietary/*"], - "@core/*": ["../../src/core/*"], - "@shared/*": ["../../../shared/*"] + "@core/*": ["../../src/core/*"] } }, "include": [ diff --git a/frontend/portal/LICENSE b/frontend/editor/src/portal/LICENSE similarity index 100% rename from frontend/portal/LICENSE rename to frontend/editor/src/portal/LICENSE diff --git a/frontend/portal/MOCKS.md b/frontend/editor/src/portal/MOCKS.md similarity index 100% rename from frontend/portal/MOCKS.md rename to frontend/editor/src/portal/MOCKS.md diff --git a/frontend/portal/src/App.tsx b/frontend/editor/src/portal/PortalApp.tsx similarity index 81% rename from frontend/portal/src/App.tsx rename to frontend/editor/src/portal/PortalApp.tsx index cd1195cde3..1494e23cdf 100644 --- a/frontend/portal/src/App.tsx +++ b/frontend/editor/src/portal/PortalApp.tsx @@ -1,12 +1,12 @@ import { useEffect, type ReactNode } from "react"; -import { BrowserRouter, useLocation } from "react-router-dom"; +import { useLocation } from "react-router-dom"; import { MantineProvider } from "@mantine/core"; -import { AuthProvider } from "@shared/auth"; +import { AuthProvider } from "@app/auth"; import { ErrorBoundary } from "@portal/components/ErrorBoundary"; import { ThemeProvider, useTheme } from "@portal/contexts/ThemeContext"; import { TierProvider } from "@portal/contexts/TierContext"; import { LinkProvider, useLink } from "@portal/contexts/LinkContext"; -import type { SupabaseLoginSession } from "@shared/auth/ui/useSupabaseLogin"; +import type { SupabaseLoginSession } from "@app/auth/ui/useSupabaseLogin"; import { UIProvider, useUI } from "@portal/contexts/UIContext"; import { mantineTheme } from "@portal/theme/mantineTheme"; import { AppShell } from "@portal/components/AppShell"; @@ -21,6 +21,8 @@ import { useAccountLinkContext, } from "@portal/contexts/AccountLinkContext"; import { ViewRouter } from "@portal/ViewRouter"; +// Reset + typography, scoped to .portal-scope below. +import "@portal/theme/base.css"; /** * Binds Mantine's colour scheme to the portal's own ThemeProvider so Mantine @@ -117,22 +119,24 @@ function RoutedContent() { ); } -export function App() { - // Honour the Vite base path so the portal routes correctly when served under a - // subpath (e.g. "/portal" behind the single-origin proxy). BASE_URL is "./" - // for a standalone build, which isn't a valid router basename, so only pass it - // when it's an absolute subpath. - const baseUrl = import.meta.env.BASE_URL; - const basename = baseUrl.startsWith("/") ? baseUrl : undefined; +/** + * The portal, mounted as a route-set under /portal/* inside the editor app (via + * the admin-route seam). It supplies its own providers and its own i18next + * instance (the `portal` namespace), but NOT a router — the editor's + * is the one and only router; the portal's routes are relative + * to the /portal mount (see ViewRouter). + */ +export function PortalApp() { return ( - - - {/* TierProvider sits INSIDE LinkProvider so it can derive the tier + {/* Scopes base.css to the portal so it doesn't restyle the host editor. */} +
+ + + {/* TierProvider sits INSIDE LinkProvider so it can derive the tier from the real link/subscription state when MSW mocks are off. */} - - + @@ -148,10 +152,10 @@ export function App() { - - - - + + + +
); diff --git a/frontend/portal/UI_CONVENTIONS.md b/frontend/editor/src/portal/UI_CONVENTIONS.md similarity index 95% rename from frontend/portal/UI_CONVENTIONS.md rename to frontend/editor/src/portal/UI_CONVENTIONS.md index c4d51b5819..40879e2199 100644 --- a/frontend/portal/UI_CONVENTIONS.md +++ b/frontend/editor/src/portal/UI_CONVENTIONS.md @@ -3,7 +3,7 @@ The portal has two component sources. The rule: > **Simple, presentational, brand-defining UI → our SUI design system -> (`@shared/components`). Complex, stateful, or accessibility-hard widgets → +> (`@app/ui`). Complex, stateful, or accessibility-hard widgets → > Mantine.** Don't reinvent what Mantine already does well; do own the look of > the simple, high-frequency pieces. @@ -13,7 +13,7 @@ follow the same light/dark switch and brand colours as SUI. **The provider is intentional** — it exists precisely so we can drop Mantine widgets in where they earn their keep. -## Use SUI (`@shared/components`) — our own style +## Use SUI (`@app/ui`) — our own style Layout and presentational primitives we want full brand control over and that are cheap to own: diff --git a/frontend/editor/src/portal/ViewRouter.tsx b/frontend/editor/src/portal/ViewRouter.tsx new file mode 100644 index 0000000000..2b09a24507 --- /dev/null +++ b/frontend/editor/src/portal/ViewRouter.tsx @@ -0,0 +1,58 @@ +import { Navigate, Route, Routes } from "react-router-dom"; +import { Home } from "@portal/views/Home"; +import { Users } from "@portal/views/Users"; +import { Documents } from "@portal/views/Documents"; +import { Pipelines } from "@portal/views/Pipelines"; +import { Sources } from "@portal/views/Sources"; +import { AgentBuilder } from "@portal/views/AgentBuilder"; +import { Policies } from "@portal/views/Policies"; +import { Components } from "@portal/views/Components"; +import { EditorAdmin } from "@portal/views/EditorAdmin"; +import { Infrastructure } from "@portal/views/Infrastructure"; +import { Usage } from "@portal/views/Usage"; +import { DeveloperDocs } from "@portal/views/DeveloperDocs"; +import { Procurement } from "@portal/views/Procurement"; +import { VIEW_PATHS, toPortalPath } from "@portal/contexts/ViewContext"; + +// The portal mounts as a route-set under /portal/* in the editor app, so these +// child routes are relative to that base: strip the leading slash from the +// logical VIEW_PATHS, and home is the index route. Redirects use toPortalPath +// so they resolve to the portal, not the editor root. +const rel = (viewPath: string) => viewPath.replace(/^\//, ""); + +export function ViewRouter() { + return ( + + } /> + } /> + } /> + } /> + } + /> + } /> + } /> + } /> + } /> + } + /> + } /> + } /> + } /> + {/* Account-link is now a Settings panel; redirect legacy bookmarks home. */} + } + /> + {/* Settings is a modal overlay, not a route (see AppShell + UIContext). */} + {/* Unknown paths land on Home. */} + } + /> + + ); +} diff --git a/frontend/portal/src/api/agents.ts b/frontend/editor/src/portal/api/agents.ts similarity index 100% rename from frontend/portal/src/api/agents.ts rename to frontend/editor/src/portal/api/agents.ts diff --git a/frontend/portal/src/api/assistant.ts b/frontend/editor/src/portal/api/assistant.ts similarity index 100% rename from frontend/portal/src/api/assistant.ts rename to frontend/editor/src/portal/api/assistant.ts diff --git a/frontend/portal/src/api/billing.ts b/frontend/editor/src/portal/api/billing.ts similarity index 95% rename from frontend/portal/src/api/billing.ts rename to frontend/editor/src/portal/api/billing.ts index 7c87bb5ca5..fbb8673a26 100644 --- a/frontend/portal/src/api/billing.ts +++ b/frontend/editor/src/portal/api/billing.ts @@ -1,10 +1,10 @@ import { apiClient } from "@portal/api/http"; -import type { Wallet } from "@shared/billing"; +import type { Wallet } from "@app/billing"; /** * Real wallet + billing surface. All calls go to apiClient.saas — the hosted * SaaS Java backend, authed by the admin's Supabase JWT. The wallet contract - * itself lives in {@code @shared/billing} (shared with the editor cloud surface). + * itself lives in {@code @app/billing} (shared with the editor cloud surface). */ // Re-export the shared contract so existing `@portal/api/billing` importers keep working. @@ -15,7 +15,7 @@ export type { WalletMember, WalletCategoryBreakdown, WalletActivityRow, -} from "@shared/billing"; +} from "@app/billing"; export async function fetchWallet(): Promise { return apiClient.saas.json("/api/v1/payg/wallet"); diff --git a/frontend/portal/src/api/docs.ts b/frontend/editor/src/portal/api/docs.ts similarity index 100% rename from frontend/portal/src/api/docs.ts rename to frontend/editor/src/portal/api/docs.ts diff --git a/frontend/portal/src/api/documents.ts b/frontend/editor/src/portal/api/documents.ts similarity index 100% rename from frontend/portal/src/api/documents.ts rename to frontend/editor/src/portal/api/documents.ts diff --git a/frontend/portal/src/api/editorDeploy.ts b/frontend/editor/src/portal/api/editorDeploy.ts similarity index 100% rename from frontend/portal/src/api/editorDeploy.ts rename to frontend/editor/src/portal/api/editorDeploy.ts diff --git a/frontend/portal/src/api/home.ts b/frontend/editor/src/portal/api/home.ts similarity index 100% rename from frontend/portal/src/api/home.ts rename to frontend/editor/src/portal/api/home.ts diff --git a/frontend/portal/src/api/http.test.ts b/frontend/editor/src/portal/api/http.test.ts similarity index 96% rename from frontend/portal/src/api/http.test.ts rename to frontend/editor/src/portal/api/http.test.ts index 085948c77f..031327c0cf 100644 --- a/frontend/portal/src/api/http.test.ts +++ b/frontend/editor/src/portal/api/http.test.ts @@ -12,8 +12,8 @@ const { getSession, getStoredTokenMock } = vi.hoisted(() => ({ getStoredTokenMock: vi.fn(), })); -vi.mock("@shared/auth", () => ({ getStoredToken: getStoredTokenMock })); -vi.mock("@shared/auth/supabase/supabaseClient", () => ({ +vi.mock("@app/auth", () => ({ getStoredToken: getStoredTokenMock })); +vi.mock("@app/auth/supabase/supabaseClient", () => ({ getSupabaseClient: () => ({ auth: { getSession } }), configureSupabase: vi.fn(), })); diff --git a/frontend/portal/src/api/http.ts b/frontend/editor/src/portal/api/http.ts similarity index 97% rename from frontend/portal/src/api/http.ts rename to frontend/editor/src/portal/api/http.ts index a37df3ce81..9f7b28da03 100644 --- a/frontend/portal/src/api/http.ts +++ b/frontend/editor/src/portal/api/http.ts @@ -5,7 +5,7 @@ * * apiClient.local Same-origin (vite proxy → this instance's local * Stirling backend on :8080). Spring admin bearer - * (`stirling_jwt` from @shared/auth) auto-attached. + * (`stirling_jwt` from @app/auth) auto-attached. * USE FOR: actions on this instance — * /api/v1/account-link/{status,link,unlink}, etc. * @@ -39,8 +39,8 @@ * entitlement calls. It never enters the portal — the browser is the human * admin and uses the Supabase JWT for SaaS reads. Don't add it here. */ -import { clearStoredToken, getStoredToken } from "@shared/auth"; -import { getSupabaseClient } from "@shared/auth/supabase/supabaseClient"; +import { clearStoredToken, getStoredToken } from "@app/auth"; +import { getSupabaseClient } from "@app/auth/supabase/supabaseClient"; import { ensureSaasSupabase } from "@portal/auth/saasSupabase"; /** Read the SaaS base URL at call time so tests can stub it via vi.stubEnv. */ diff --git a/frontend/portal/src/api/infrastructure.ts b/frontend/editor/src/portal/api/infrastructure.ts similarity index 100% rename from frontend/portal/src/api/infrastructure.ts rename to frontend/editor/src/portal/api/infrastructure.ts diff --git a/frontend/portal/src/api/link.test.ts b/frontend/editor/src/portal/api/link.test.ts similarity index 98% rename from frontend/portal/src/api/link.test.ts rename to frontend/editor/src/portal/api/link.test.ts index e6d9b12703..2f5260486a 100644 --- a/frontend/portal/src/api/link.test.ts +++ b/frontend/editor/src/portal/api/link.test.ts @@ -20,7 +20,7 @@ const { getSession } = vi.hoisted(() => ({ data: { session: { access_token: "supabase_jwt_test" } }, }), })); -vi.mock("@shared/auth/supabase/supabaseClient", () => ({ +vi.mock("@app/auth/supabase/supabaseClient", () => ({ getSupabaseClient: () => ({ auth: { getSession } }), configureSupabase: vi.fn(), })); diff --git a/frontend/portal/src/api/link.ts b/frontend/editor/src/portal/api/link.ts similarity index 100% rename from frontend/portal/src/api/link.ts rename to frontend/editor/src/portal/api/link.ts diff --git a/frontend/portal/src/api/notifications.ts b/frontend/editor/src/portal/api/notifications.ts similarity index 100% rename from frontend/portal/src/api/notifications.ts rename to frontend/editor/src/portal/api/notifications.ts diff --git a/frontend/portal/src/api/ops.ts b/frontend/editor/src/portal/api/ops.ts similarity index 100% rename from frontend/portal/src/api/ops.ts rename to frontend/editor/src/portal/api/ops.ts diff --git a/frontend/portal/src/api/pipelines.ts b/frontend/editor/src/portal/api/pipelines.ts similarity index 100% rename from frontend/portal/src/api/pipelines.ts rename to frontend/editor/src/portal/api/pipelines.ts diff --git a/frontend/portal/src/api/policies.ts b/frontend/editor/src/portal/api/policies.ts similarity index 95% rename from frontend/portal/src/api/policies.ts rename to frontend/editor/src/portal/api/policies.ts index 70eccd5cfb..e225449a16 100644 --- a/frontend/portal/src/api/policies.ts +++ b/frontend/editor/src/portal/api/policies.ts @@ -11,9 +11,9 @@ */ import { apiClient } from "@portal/api/http"; -import { fromWirePolicy, toWirePolicy } from "@shared/policies/codec"; -import { runsToActivity, runsToStats } from "@shared/policies/runs"; -import type { PolicyDecodedState, WirePolicy } from "@shared/policies/types"; +import { fromWirePolicy, toWirePolicy } from "@app/policies/codec"; +import { runsToActivity, runsToStats } from "@app/policies/runs"; +import type { PolicyDecodedState, WirePolicy } from "@app/policies/types"; import { POLICY_CATEGORIES, POLICY_CONFIG, @@ -25,7 +25,7 @@ import { type PolicyState, type PolicyStatus, } from "@portal/mocks/policies"; -import type { PolicyRunView } from "@shared/policies/types"; +import type { PolicyRunView } from "@app/policies/types"; export type { CatalogueEntry, @@ -58,7 +58,7 @@ export { } from "@portal/mocks/policies"; // Re-export the wire step type under the legacy name components depend on. -export type { WirePipelineStep as PipelineStep } from "@shared/policies/types"; +export type { WirePipelineStep as PipelineStep } from "@app/policies/types"; // ── Client-side catalogue assembly ─────────────────────────────────────────── diff --git a/frontend/portal/src/api/procurement.ts b/frontend/editor/src/portal/api/procurement.ts similarity index 100% rename from frontend/portal/src/api/procurement.ts rename to frontend/editor/src/portal/api/procurement.ts diff --git a/frontend/portal/src/api/sdkComponents.ts b/frontend/editor/src/portal/api/sdkComponents.ts similarity index 100% rename from frontend/portal/src/api/sdkComponents.ts rename to frontend/editor/src/portal/api/sdkComponents.ts diff --git a/frontend/portal/src/api/search.ts b/frontend/editor/src/portal/api/search.ts similarity index 100% rename from frontend/portal/src/api/search.ts rename to frontend/editor/src/portal/api/search.ts diff --git a/frontend/portal/src/api/settings.ts b/frontend/editor/src/portal/api/settings.ts similarity index 100% rename from frontend/portal/src/api/settings.ts rename to frontend/editor/src/portal/api/settings.ts diff --git a/frontend/portal/src/api/sources.ts b/frontend/editor/src/portal/api/sources.ts similarity index 100% rename from frontend/portal/src/api/sources.ts rename to frontend/editor/src/portal/api/sources.ts diff --git a/frontend/portal/src/api/users.ts b/frontend/editor/src/portal/api/users.ts similarity index 100% rename from frontend/portal/src/api/users.ts rename to frontend/editor/src/portal/api/users.ts diff --git a/frontend/portal/src/auth/editorUrl.ts b/frontend/editor/src/portal/auth/editorUrl.ts similarity index 86% rename from frontend/portal/src/auth/editorUrl.ts rename to frontend/editor/src/portal/auth/editorUrl.ts index 2cf77c75b1..43eb466cb7 100644 --- a/frontend/portal/src/auth/editorUrl.ts +++ b/frontend/editor/src/portal/auth/editorUrl.ts @@ -5,6 +5,6 @@ * Sourced from VITE_EDITOR_URL so it's configurable per deploy rather than * hardcoded. The committed default is "/" (production serves the editor at the * root on the same origin as the portal). For dev cross-app navigation to a - * separately-running editor, set VITE_EDITOR_URL in portal/.env.local. + * separately-running editor, set VITE_EDITOR_URL in editor/.env.local. */ export const EDITOR_URL = import.meta.env.VITE_EDITOR_URL; diff --git a/frontend/portal/src/auth/saasSupabase.ts b/frontend/editor/src/portal/auth/saasSupabase.ts similarity index 97% rename from frontend/portal/src/auth/saasSupabase.ts rename to frontend/editor/src/portal/auth/saasSupabase.ts index 25a8a6ed6b..813a2f7cad 100644 --- a/frontend/portal/src/auth/saasSupabase.ts +++ b/frontend/editor/src/portal/auth/saasSupabase.ts @@ -1,7 +1,7 @@ import { configureSupabase, getSupabaseClient, -} from "@shared/auth/supabase/supabaseClient"; +} from "@app/auth/supabase/supabaseClient"; /** * Configures the shared Supabase client against the hosted SaaS project so the diff --git a/frontend/portal/src/auth/saasSupabaseLogin.test.ts b/frontend/editor/src/portal/auth/saasSupabaseLogin.test.ts similarity index 96% rename from frontend/portal/src/auth/saasSupabaseLogin.test.ts rename to frontend/editor/src/portal/auth/saasSupabaseLogin.test.ts index 5517897ec9..f1d991a3c0 100644 --- a/frontend/portal/src/auth/saasSupabaseLogin.test.ts +++ b/frontend/editor/src/portal/auth/saasSupabaseLogin.test.ts @@ -8,11 +8,11 @@ const { signInWithPassword, signInWithOAuth } = vi.hoisted(() => ({ signInWithOAuth: vi.fn(), })); -vi.mock("@shared/auth/supabase/supabaseClient", () => ({ +vi.mock("@app/auth/supabase/supabaseClient", () => ({ getSupabaseClient: () => ({ auth: { signInWithPassword, signInWithOAuth } }), })); -import { useSupabaseLogin } from "@shared/auth/ui/useSupabaseLogin"; +import { useSupabaseLogin } from "@app/auth/ui/useSupabaseLogin"; describe("useSupabaseLogin (in-app account-link login)", () => { beforeEach(() => { diff --git a/frontend/portal/src/billing/sharedBillingFormat.test.ts b/frontend/editor/src/portal/billing/sharedBillingFormat.test.ts similarity index 96% rename from frontend/portal/src/billing/sharedBillingFormat.test.ts rename to frontend/editor/src/portal/billing/sharedBillingFormat.test.ts index 8882df796b..d7ccd22df7 100644 --- a/frontend/portal/src/billing/sharedBillingFormat.test.ts +++ b/frontend/editor/src/portal/billing/sharedBillingFormat.test.ts @@ -5,10 +5,10 @@ import { formatMinor, formatPeriodDate, meterState, -} from "@shared/billing"; +} from "@app/billing"; /** - * Unit tests for the @shared/billing money/meter helpers the portal billing + * Unit tests for the @app/billing money/meter helpers the portal billing * surface (and the editor cloud surface) depend on. docCapForMoney mirrors the * backend's cap→PDF conversion and meterState mirrors the BE warn/degrade bands, * so these invariants matter beyond cosmetics. diff --git a/frontend/portal/src/billing/stripe.test.ts b/frontend/editor/src/portal/billing/stripe.test.ts similarity index 98% rename from frontend/portal/src/billing/stripe.test.ts rename to frontend/editor/src/portal/billing/stripe.test.ts index eeb9e16dcd..53a732fb63 100644 --- a/frontend/portal/src/billing/stripe.test.ts +++ b/frontend/editor/src/portal/billing/stripe.test.ts @@ -11,7 +11,7 @@ const { getClient, invoke } = vi.hoisted(() => ({ })); vi.mock("@portal/auth/saasSupabase", () => ({ ensureSaasSupabase: vi.fn() })); -vi.mock("@shared/auth/supabase/supabaseClient", () => ({ +vi.mock("@app/auth/supabase/supabaseClient", () => ({ getSupabaseClient: () => getClient(), configureSupabase: vi.fn(), })); diff --git a/frontend/portal/src/billing/stripe.ts b/frontend/editor/src/portal/billing/stripe.ts similarity index 98% rename from frontend/portal/src/billing/stripe.ts rename to frontend/editor/src/portal/billing/stripe.ts index 068046ef37..87fa82cf7b 100644 --- a/frontend/portal/src/billing/stripe.ts +++ b/frontend/editor/src/portal/billing/stripe.ts @@ -1,4 +1,4 @@ -import { getSupabaseClient } from "@shared/auth/supabase/supabaseClient"; +import { getSupabaseClient } from "@app/auth/supabase/supabaseClient"; import { ensureSaasSupabase } from "@portal/auth/saasSupabase"; /** diff --git a/frontend/portal/src/components/AppShell.css b/frontend/editor/src/portal/components/AppShell.css similarity index 100% rename from frontend/portal/src/components/AppShell.css rename to frontend/editor/src/portal/components/AppShell.css diff --git a/frontend/portal/src/components/AppShell.tsx b/frontend/editor/src/portal/components/AppShell.tsx similarity index 100% rename from frontend/portal/src/components/AppShell.tsx rename to frontend/editor/src/portal/components/AppShell.tsx diff --git a/frontend/portal/src/components/AssistantButton.css b/frontend/editor/src/portal/components/AssistantButton.css similarity index 100% rename from frontend/portal/src/components/AssistantButton.css rename to frontend/editor/src/portal/components/AssistantButton.css diff --git a/frontend/portal/src/components/AssistantButton.tsx b/frontend/editor/src/portal/components/AssistantButton.tsx similarity index 87% rename from frontend/portal/src/components/AssistantButton.tsx rename to frontend/editor/src/portal/components/AssistantButton.tsx index e88130094b..7f3586c8cc 100644 --- a/frontend/portal/src/components/AssistantButton.tsx +++ b/frontend/editor/src/portal/components/AssistantButton.tsx @@ -11,9 +11,11 @@ export function AssistantButton() { type="button" className={"portal-assistant-btn" + (assistantOpen ? " is-active" : "")} onClick={toggleAssistant} - aria-label={assistantOpen ? t("assistant.close") : t("assistant.open")} + aria-label={ + assistantOpen ? t("portal.assistant.close") : t("portal.assistant.open") + } aria-expanded={assistantOpen} - title={t("assistant.title")} + title={t("portal.assistant.title")} > {assistantOpen ? ( [...prev, failMsg]); } finally { @@ -86,20 +86,20 @@ export function AssistantPanel() {
- {t("assistant.title")} + {t("portal.assistant.title")}
@@ -109,7 +109,7 @@ export function AssistantPanel() { {messages.length === 0 && suggestions && (
- {t("assistant.tryAsking")} + {t("portal.assistant.tryAsking")}
{suggestions.map((s) => ( @@ -140,7 +140,7 @@ export function AssistantPanel() {
@@ -161,8 +161,8 @@ export function AssistantPanel() { ref={inputRef} value={input} onChange={(e) => setInput(e.target.value)} - placeholder={t("assistant.inputPlaceholder")} - aria-label={t("assistant.inputAriaLabel")} + placeholder={t("portal.assistant.inputPlaceholder")} + aria-label={t("portal.assistant.inputAriaLabel")} className="portal-assistant__input" disabled={typing} /> @@ -170,7 +170,7 @@ export function AssistantPanel() { type="submit" className="portal-assistant__send" disabled={!input.trim() || typing} - aria-label={t("assistant.send")} + aria-label={t("portal.assistant.send")} > diff --git a/frontend/portal/src/components/AuthGate.tsx b/frontend/editor/src/portal/components/AuthGate.tsx similarity index 83% rename from frontend/portal/src/components/AuthGate.tsx rename to frontend/editor/src/portal/components/AuthGate.tsx index 6d923abd12..a7f4e58ef5 100644 --- a/frontend/portal/src/components/AuthGate.tsx +++ b/frontend/editor/src/portal/components/AuthGate.tsx @@ -1,7 +1,7 @@ import { type ReactNode } from "react"; import { useTranslation } from "react-i18next"; -import { RequirePortalAccess } from "@shared/auth"; -import { Spinner } from "@shared/components"; +import { RequirePortalAccess } from "@app/auth"; +import { Spinner } from "@app/ui"; import { LoginScreen } from "@portal/components/LoginScreen"; import { EDITOR_URL } from "@portal/auth/editorUrl"; @@ -36,12 +36,12 @@ export function AuthGate({ children }: { children: ReactNode }) { onForbidden={redirectToEditor} loading={ - + } forbidden={ - {t("auth.redirectingToEditor", "Redirecting to the editor...")} + {t("portal.auth.redirectingToEditor", "Redirecting to the editor...")} } > diff --git a/frontend/portal/src/components/ChatFABWidget.stories.tsx b/frontend/editor/src/portal/components/ChatFABWidget.stories.tsx similarity index 98% rename from frontend/portal/src/components/ChatFABWidget.stories.tsx rename to frontend/editor/src/portal/components/ChatFABWidget.stories.tsx index 1d49dc8e5b..db9b006173 100644 --- a/frontend/portal/src/components/ChatFABWidget.stories.tsx +++ b/frontend/editor/src/portal/components/ChatFABWidget.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { useRef, useState } from "react"; -import { ChatFABButton } from "@shared/components/ChatFABButton"; -import { ChatFABWindow } from "@shared/components/ChatFABWindow"; +import { ChatFABButton } from "@app/ui/ChatFABButton"; +import { ChatFABWindow } from "@app/ui/ChatFABWindow"; /** * Full ChatFAB widget demo — composed from ChatFABButton + ChatFABWindow. diff --git a/frontend/portal/src/components/ErrorBoundary.test.tsx b/frontend/editor/src/portal/components/ErrorBoundary.test.tsx similarity index 81% rename from frontend/portal/src/components/ErrorBoundary.test.tsx rename to frontend/editor/src/portal/components/ErrorBoundary.test.tsx index 91c22effca..4e84344e77 100644 --- a/frontend/portal/src/components/ErrorBoundary.test.tsx +++ b/frontend/editor/src/portal/components/ErrorBoundary.test.tsx @@ -25,8 +25,10 @@ describe("ErrorBoundary", () => { , ); - expect(screen.getByText("errorBoundary.title")).toBeInTheDocument(); - expect(screen.getByText("errorBoundary.description")).toBeInTheDocument(); + expect(screen.getByText("portal.errorBoundary.title")).toBeInTheDocument(); + expect( + screen.getByText("portal.errorBoundary.description"), + ).toBeInTheDocument(); spy.mockRestore(); }); @@ -51,14 +53,16 @@ describe("ErrorBoundary", () => { ); // Fallback is shown. - expect(screen.getByText("errorBoundary.title")).toBeInTheDocument(); + expect(screen.getByText("portal.errorBoundary.title")).toBeInTheDocument(); // Stop throwing, then click retry to clear the boundary's error state. shouldThrow = false; - fireEvent.click(screen.getByText("errorBoundary.retry")); + fireEvent.click(screen.getByText("portal.errorBoundary.retry")); expect(screen.getByText("recovered content")).toBeInTheDocument(); - expect(screen.queryByText("errorBoundary.title")).not.toBeInTheDocument(); + expect( + screen.queryByText("portal.errorBoundary.title"), + ).not.toBeInTheDocument(); spy.mockRestore(); }); }); diff --git a/frontend/portal/src/components/ErrorBoundary.tsx b/frontend/editor/src/portal/components/ErrorBoundary.tsx similarity index 88% rename from frontend/portal/src/components/ErrorBoundary.tsx rename to frontend/editor/src/portal/components/ErrorBoundary.tsx index f97d208a31..4c5dad9b0e 100644 --- a/frontend/portal/src/components/ErrorBoundary.tsx +++ b/frontend/editor/src/portal/components/ErrorBoundary.tsx @@ -1,6 +1,6 @@ import { Component, type ErrorInfo, type ReactNode } from "react"; import { useTranslation } from "react-i18next"; -import { Button, EmptyState } from "@shared/components"; +import { Button, EmptyState } from "@app/ui"; /** * Default fallback for {@link ErrorBoundary}. Split into a function component so @@ -11,9 +11,11 @@ function DefaultErrorFallback({ onRetry }: { onRetry: () => void }) { const { t } = useTranslation(); return ( {t("errorBoundary.retry")}} + title={t("portal.errorBoundary.title")} + description={t("portal.errorBoundary.description")} + actions={ + + } /> ); } diff --git a/frontend/portal/src/components/Header.css b/frontend/editor/src/portal/components/Header.css similarity index 100% rename from frontend/portal/src/components/Header.css rename to frontend/editor/src/portal/components/Header.css diff --git a/frontend/portal/src/components/Header.stories.tsx b/frontend/editor/src/portal/components/Header.stories.tsx similarity index 100% rename from frontend/portal/src/components/Header.stories.tsx rename to frontend/editor/src/portal/components/Header.stories.tsx diff --git a/frontend/portal/src/components/Header.tsx b/frontend/editor/src/portal/components/Header.tsx similarity index 85% rename from frontend/portal/src/components/Header.tsx rename to frontend/editor/src/portal/components/Header.tsx index 8783e9bafc..7194094171 100644 --- a/frontend/portal/src/components/Header.tsx +++ b/frontend/editor/src/portal/components/Header.tsx @@ -1,6 +1,6 @@ import { useTranslation } from "react-i18next"; -import { Avatar, Dropdown } from "@shared/components"; -import { useAuth } from "@shared/auth"; +import { Avatar, Dropdown } from "@app/ui"; +import { useAuth } from "@app/auth"; import { useTheme } from "@portal/contexts/ThemeContext"; import { useTier, TIER_INFO, type Tier } from "@portal/contexts/TierContext"; import { useView } from "@portal/contexts/ViewContext"; @@ -25,13 +25,13 @@ function ThemeToggle() { onClick={toggle} aria-label={ theme === "light" - ? t("shell.header.switchToDark") - : t("shell.header.switchToLight") + ? t("portal.shell.header.switchToDark") + : t("portal.shell.header.switchToLight") } title={ theme === "light" - ? t("shell.header.darkMode") - : t("shell.header.lightMode") + ? t("portal.shell.header.darkMode") + : t("portal.shell.header.lightMode") } > {theme === "light" ? : } @@ -84,14 +84,15 @@ function TierSwitcher() { function UserMenu() { const { t } = useTranslation(); const { displayName, signOut } = useAuth(); - const name = displayName ?? t("shell.header.accountFallback", "Account"); + const name = + displayName ?? t("portal.shell.header.accountFallback", "Account"); return ( ); diff --git a/frontend/portal/src/components/NotificationsDropdown.css b/frontend/editor/src/portal/components/NotificationsDropdown.css similarity index 100% rename from frontend/portal/src/components/NotificationsDropdown.css rename to frontend/editor/src/portal/components/NotificationsDropdown.css diff --git a/frontend/portal/src/components/NotificationsDropdown.stories.tsx b/frontend/editor/src/portal/components/NotificationsDropdown.stories.tsx similarity index 100% rename from frontend/portal/src/components/NotificationsDropdown.stories.tsx rename to frontend/editor/src/portal/components/NotificationsDropdown.stories.tsx diff --git a/frontend/portal/src/components/NotificationsDropdown.tsx b/frontend/editor/src/portal/components/NotificationsDropdown.tsx similarity index 85% rename from frontend/portal/src/components/NotificationsDropdown.tsx rename to frontend/editor/src/portal/components/NotificationsDropdown.tsx index c93a2f9f33..4a0d84d5c3 100644 --- a/frontend/portal/src/components/NotificationsDropdown.tsx +++ b/frontend/editor/src/portal/components/NotificationsDropdown.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { Dropdown, EmptyState, Skeleton } from "@shared/components"; +import { Dropdown, EmptyState, Skeleton } from "@app/ui"; import { BellIcon } from "@portal/components/icons"; import { useAsync, useSectionFlags } from "@portal/hooks/useAsync"; import { @@ -50,10 +50,10 @@ export function NotificationsDropdown() { className="portal-header__icon-btn portal-header__icon-btn--badge" aria-label={ hasUnread - ? t("notifications.ariaLabel.unread", { + ? t("portal.notifications.ariaLabel.unread", { count: visible.length, }) - : t("notifications.ariaLabel.none") + : t("portal.notifications.ariaLabel.none") } > @@ -65,19 +65,19 @@ export function NotificationsDropdown() {
- {t("notifications.title")} + {t("portal.notifications.title")} {hasUnread ? ( - {t("notifications.count.new", { count: visible.length })} + {t("portal.notifications.count.new", { count: visible.length })} ) : isLoading ? ( - {t("notifications.count.loading")} + {t("portal.notifications.count.loading")} ) : ( - {t("notifications.count.allRead")} + {t("portal.notifications.count.allRead")} )}
@@ -92,8 +92,8 @@ export function NotificationsDropdown() { {isEmpty && ( )} {!isLoading && !isEmpty && ( @@ -121,10 +121,10 @@ export function NotificationsDropdown() { onClick={onMarkAllRead} disabled={!hasUnread} > - {t("notifications.markAllRead")} + {t("portal.notifications.markAllRead")}
diff --git a/frontend/portal/src/components/PipelineForkWizard.css b/frontend/editor/src/portal/components/PipelineForkWizard.css similarity index 100% rename from frontend/portal/src/components/PipelineForkWizard.css rename to frontend/editor/src/portal/components/PipelineForkWizard.css diff --git a/frontend/portal/src/components/PipelineForkWizard.stories.tsx b/frontend/editor/src/portal/components/PipelineForkWizard.stories.tsx similarity index 100% rename from frontend/portal/src/components/PipelineForkWizard.stories.tsx rename to frontend/editor/src/portal/components/PipelineForkWizard.stories.tsx diff --git a/frontend/portal/src/components/PipelineForkWizard.tsx b/frontend/editor/src/portal/components/PipelineForkWizard.tsx similarity index 90% rename from frontend/portal/src/components/PipelineForkWizard.tsx rename to frontend/editor/src/portal/components/PipelineForkWizard.tsx index a357a6bb75..838afda3db 100644 --- a/frontend/portal/src/components/PipelineForkWizard.tsx +++ b/frontend/editor/src/portal/components/PipelineForkWizard.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Button, Card, Chip, StatusBadge } from "@shared/components"; +import { Button, Card, Chip, StatusBadge } from "@app/ui"; import { useView } from "@portal/contexts/ViewContext"; import { PIPELINE_STAGES, @@ -70,14 +70,14 @@ export function PipelineForkWizard() {
-

{t("forkWizard.title")}

-

{t("forkWizard.subtitle")}

+

{t("portal.forkWizard.title")}

+

{t("portal.forkWizard.subtitle")}

{phase !== "pick" && template && ( {phase === "ready" - ? t("forkWizard.status.ready") - : t("forkWizard.status.building")} + ? t("portal.forkWizard.status.ready") + : t("portal.forkWizard.status.building")} )}
@@ -141,8 +141,8 @@ export function PipelineForkWizard() {
diff --git a/frontend/portal/src/components/PolicySummary.css b/frontend/editor/src/portal/components/PolicySummary.css similarity index 100% rename from frontend/portal/src/components/PolicySummary.css rename to frontend/editor/src/portal/components/PolicySummary.css diff --git a/frontend/portal/src/components/PolicySummary.stories.tsx b/frontend/editor/src/portal/components/PolicySummary.stories.tsx similarity index 100% rename from frontend/portal/src/components/PolicySummary.stories.tsx rename to frontend/editor/src/portal/components/PolicySummary.stories.tsx diff --git a/frontend/portal/src/components/PolicySummary.tsx b/frontend/editor/src/portal/components/PolicySummary.tsx similarity index 81% rename from frontend/portal/src/components/PolicySummary.tsx rename to frontend/editor/src/portal/components/PolicySummary.tsx index 57199bc8f7..084e7e5b69 100644 --- a/frontend/portal/src/components/PolicySummary.tsx +++ b/frontend/editor/src/portal/components/PolicySummary.tsx @@ -7,7 +7,7 @@ import { StatusBadge, Table, type TableColumn, -} from "@shared/components"; +} from "@app/ui"; import { useView } from "@portal/contexts/ViewContext"; import { useAsync, useSectionFlags } from "@portal/hooks/useAsync"; import { @@ -37,9 +37,9 @@ const STATE_BADGE: Record< RowState, { tone: "success" | "neutral" | "info"; labelKey: string } > = { - active: { tone: "success", labelKey: "policySummary.state.active" }, - off: { tone: "neutral", labelKey: "policySummary.state.off" }, - locked: { tone: "info", labelKey: "policySummary.state.soon" }, + active: { tone: "success", labelKey: "portal.policySummary.state.active" }, + off: { tone: "neutral", labelKey: "portal.policySummary.state.off" }, + locked: { tone: "info", labelKey: "portal.policySummary.state.soon" }, }; function toRow(entry: CatalogueEntry): PolicyRow { @@ -60,7 +60,7 @@ export function PolicySummary() { const columns: TableColumn[] = [ { key: "category", - header: t("policySummary.column.policy"), + header: t("portal.policySummary.column.policy"), render: ({ entry }) => (
@@ -75,7 +75,7 @@ export function PolicySummary() { }, { key: "status", - header: t("policySummary.column.status"), + header: t("portal.policySummary.column.status"), width: "7rem", render: ({ state }) => { const badge = STATE_BADGE[state]; @@ -88,12 +88,12 @@ export function PolicySummary() { }, { key: "rule", - header: t("policySummary.column.activeRule"), + header: t("portal.policySummary.column.activeRule"), render: ({ entry, state }) => ( {state === "active" ? entry.config.summary - : t("policySummary.noRule")} + : t("portal.policySummary.noRule")} ), }, @@ -106,7 +106,7 @@ export function PolicySummary() { if (state === "locked") { return ( ); } @@ -117,8 +117,8 @@ export function PolicySummary() { onClick={goToPolicies} > {state === "active" - ? t("policySummary.action.configure") - : t("policySummary.action.setUp")} + ? t("portal.policySummary.action.configure") + : t("portal.policySummary.action.setUp")} ); }, @@ -128,20 +128,23 @@ export function PolicySummary() { const rows: PolicyRow[] = data?.catalogue?.map(toRow) ?? []; return ( -
+

- {t("policySummary.title")} + {t("portal.policySummary.title")}

- {t("policySummary.subtitle")} + {t("portal.policySummary.subtitle")}

{data?.summary && ( - {t("policySummary.activeSummary", { + {t("portal.policySummary.activeSummary", { active: data.summary.active, total: data.summary.categories, })} @@ -163,8 +166,8 @@ export function PolicySummary() { {isEmpty && ( )} diff --git a/frontend/portal/src/components/PopularUseCases.css b/frontend/editor/src/portal/components/PopularUseCases.css similarity index 100% rename from frontend/portal/src/components/PopularUseCases.css rename to frontend/editor/src/portal/components/PopularUseCases.css diff --git a/frontend/portal/src/components/PopularUseCases.tsx b/frontend/editor/src/portal/components/PopularUseCases.tsx similarity index 79% rename from frontend/portal/src/components/PopularUseCases.tsx rename to frontend/editor/src/portal/components/PopularUseCases.tsx index 6f99f74453..b85912761f 100644 --- a/frontend/portal/src/components/PopularUseCases.tsx +++ b/frontend/editor/src/portal/components/PopularUseCases.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { Card, type CardProps } from "@shared/components"; +import { Card, type CardProps } from "@app/ui"; import { useView } from "@portal/contexts/ViewContext"; import "@portal/components/PopularUseCases.css"; @@ -36,15 +36,18 @@ export function PopularUseCases() { const { t } = useTranslation(); const { setActiveView } = useView(); return ( -
+
-

{t("useCases.title")}

+

{t("portal.useCases.title")}

@@ -59,13 +62,13 @@ export function PopularUseCases() { className="portal-usecases__eyebrow" style={{ color: ACCENT_COLOR[uc.accent] }} > - {t(`useCases.items.${uc.key}.eyebrow`)} + {t(`portal.useCases.items.${uc.key}.eyebrow`)}

- {t(`useCases.items.${uc.key}.title`)} + {t(`portal.useCases.items.${uc.key}.title`)}

- {t(`useCases.items.${uc.key}.blurb`)} + {t(`portal.useCases.items.${uc.key}.blurb`)}

))} diff --git a/frontend/portal/src/components/ProcessingStatusStrip.css b/frontend/editor/src/portal/components/ProcessingStatusStrip.css similarity index 100% rename from frontend/portal/src/components/ProcessingStatusStrip.css rename to frontend/editor/src/portal/components/ProcessingStatusStrip.css diff --git a/frontend/portal/src/components/ProcessingStatusStrip.stories.tsx b/frontend/editor/src/portal/components/ProcessingStatusStrip.stories.tsx similarity index 100% rename from frontend/portal/src/components/ProcessingStatusStrip.stories.tsx rename to frontend/editor/src/portal/components/ProcessingStatusStrip.stories.tsx diff --git a/frontend/portal/src/components/ProcessingStatusStrip.tsx b/frontend/editor/src/portal/components/ProcessingStatusStrip.tsx similarity index 89% rename from frontend/portal/src/components/ProcessingStatusStrip.tsx rename to frontend/editor/src/portal/components/ProcessingStatusStrip.tsx index 05adba3e40..9aa3025f98 100644 --- a/frontend/portal/src/components/ProcessingStatusStrip.tsx +++ b/frontend/editor/src/portal/components/ProcessingStatusStrip.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { Banner, Button, ProgressBar, Skeleton } from "@shared/components"; +import { Banner, Button, ProgressBar, Skeleton } from "@app/ui"; import { TIER_INFO, useTier } from "@portal/contexts/TierContext"; import { useView } from "@portal/contexts/ViewContext"; import { useAsync } from "@portal/hooks/useAsync"; @@ -60,7 +60,7 @@ export function ProcessingStatusStrip() { variant="outline" onClick={() => setActiveView("usage")} > - {t("processingStatus.upgrade")} + {t("portal.processingStatus.upgrade")} ) : undefined } @@ -68,7 +68,7 @@ export function ProcessingStatusStrip() {
{used.toLocaleString()} / {cap.toLocaleString()}{" "} - {t("processingStatus.pdfsThisMonth")} + {t("portal.processingStatus.pdfsThisMonth")} {Math.round(ratio * 100)}% @@ -77,7 +77,7 @@ export function ProcessingStatusStrip() { ); @@ -99,7 +99,8 @@ export function ProcessingStatusStrip() { · - {volume ?? "—"} {t("processingStatus.volumeSuffix")} + {volume ?? "—"}{" "} + {t("portal.processingStatus.volumeSuffix")}
); diff --git a/frontend/portal/src/components/RecentActivity.css b/frontend/editor/src/portal/components/RecentActivity.css similarity index 100% rename from frontend/portal/src/components/RecentActivity.css rename to frontend/editor/src/portal/components/RecentActivity.css diff --git a/frontend/portal/src/components/RecentActivity.stories.tsx b/frontend/editor/src/portal/components/RecentActivity.stories.tsx similarity index 100% rename from frontend/portal/src/components/RecentActivity.stories.tsx rename to frontend/editor/src/portal/components/RecentActivity.stories.tsx diff --git a/frontend/portal/src/components/RecentActivity.tsx b/frontend/editor/src/portal/components/RecentActivity.tsx similarity index 87% rename from frontend/portal/src/components/RecentActivity.tsx rename to frontend/editor/src/portal/components/RecentActivity.tsx index 024eb2f6ce..8934a5f0bd 100644 --- a/frontend/portal/src/components/RecentActivity.tsx +++ b/frontend/editor/src/portal/components/RecentActivity.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { Card, EmptyState, Skeleton, StatusBadge } from "@shared/components"; +import { Card, EmptyState, Skeleton, StatusBadge } from "@app/ui"; import { useAsync, useSectionFlags } from "@portal/hooks/useAsync"; import { fetchRecentActivity, @@ -27,12 +27,14 @@ export function RecentActivity() {
-

{t("recentActivity.title")}

+

+ {t("portal.recentActivity.title")} +

@@ -56,8 +58,8 @@ export function RecentActivity() { {isEmpty && ( )} diff --git a/frontend/portal/src/components/SearchModal.css b/frontend/editor/src/portal/components/SearchModal.css similarity index 100% rename from frontend/portal/src/components/SearchModal.css rename to frontend/editor/src/portal/components/SearchModal.css diff --git a/frontend/portal/src/components/SearchModal.stories.tsx b/frontend/editor/src/portal/components/SearchModal.stories.tsx similarity index 100% rename from frontend/portal/src/components/SearchModal.stories.tsx rename to frontend/editor/src/portal/components/SearchModal.stories.tsx diff --git a/frontend/portal/src/components/SearchModal.tsx b/frontend/editor/src/portal/components/SearchModal.tsx similarity index 88% rename from frontend/portal/src/components/SearchModal.tsx rename to frontend/editor/src/portal/components/SearchModal.tsx index df27a86732..ca66f9365e 100644 --- a/frontend/portal/src/components/SearchModal.tsx +++ b/frontend/editor/src/portal/components/SearchModal.tsx @@ -1,6 +1,6 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import { EmptyState, Modal, Skeleton } from "@shared/components"; +import { EmptyState, Modal, Skeleton } from "@app/ui"; import { useUI } from "@portal/contexts/UIContext"; import { useAsync, useSectionFlags } from "@portal/hooks/useAsync"; import { fetchQuickActions, type QuickAction } from "@portal/api/search"; @@ -50,7 +50,7 @@ export function SearchModal() { open={searchOpen} onClose={closeSearch} width="lg" - ariaLabel={t("search.ariaLabel")} + ariaLabel={t("portal.search.ariaLabel")} >
@@ -59,8 +59,8 @@ export function SearchModal() { ref={inputRef} value={query} onChange={(e) => setQuery(e.target.value)} - placeholder={t("search.placeholder")} - aria-label={t("search.ariaLabel")} + placeholder={t("portal.search.placeholder")} + aria-label={t("portal.search.ariaLabel")} className="portal-search__input" autoComplete="off" spellCheck={false} @@ -84,13 +84,13 @@ export function SearchModal() { size="compact" title={ query.trim() - ? t("search.empty.noMatches", { query: query.trim() }) - : t("search.empty.noActionsTitle") + ? t("portal.search.empty.noMatches", { query: query.trim() }) + : t("portal.search.empty.noActionsTitle") } description={ query.trim() - ? t("search.empty.noMatchesDescription") - : t("search.empty.noActionsDescription") + ? t("portal.search.empty.noMatchesDescription") + : t("portal.search.empty.noActionsDescription") } /> )} diff --git a/frontend/portal/src/components/SettingsModal.css b/frontend/editor/src/portal/components/SettingsModal.css similarity index 100% rename from frontend/portal/src/components/SettingsModal.css rename to frontend/editor/src/portal/components/SettingsModal.css diff --git a/frontend/portal/src/components/SettingsModal.tsx b/frontend/editor/src/portal/components/SettingsModal.tsx similarity index 85% rename from frontend/portal/src/components/SettingsModal.tsx rename to frontend/editor/src/portal/components/SettingsModal.tsx index 7cb4ad8c44..aa01c2ba0e 100644 --- a/frontend/portal/src/components/SettingsModal.tsx +++ b/frontend/editor/src/portal/components/SettingsModal.tsx @@ -13,7 +13,7 @@ import { ToggleSwitch, type SelectOption, type SettingsNavSection, -} from "@shared/components"; +} from "@app/ui"; import { useTier, type Tier } from "@portal/contexts/TierContext"; import { useTheme, type Theme } from "@portal/contexts/ThemeContext"; import { useAsync } from "@portal/hooks/useAsync"; @@ -118,56 +118,56 @@ export function SettingsModal({ const navSections = useMemo( () => [ { - title: t("settings.groups.account"), + title: t("portal.settings.groups.account"), items: [ { key: "profile", - label: t("settings.sections.profile"), + label: t("portal.settings.sections.profile"), icon: , }, { key: "appearance", - label: t("settings.sections.appearance"), + label: t("portal.settings.sections.appearance"), icon: , }, { key: "notifications", - label: t("settings.sections.notifications"), + label: t("portal.settings.sections.notifications"), icon: , }, ], }, { - title: t("settings.groups.workspace"), + title: t("portal.settings.groups.workspace"), items: [ { key: "general", - label: t("settings.sections.general"), + label: t("portal.settings.sections.general"), icon: , }, ], }, { - title: t("settings.groups.admin"), + title: t("portal.settings.groups.admin"), items: [ { key: "account-link", - label: t("settings.sections.account-link"), + label: t("portal.settings.sections.account-link"), icon: , }, { key: "authentication", - label: t("settings.sections.authentication"), + label: t("portal.settings.sections.authentication"), icon: , }, { key: "sessions", - label: t("settings.sections.sessions"), + label: t("portal.settings.sections.sessions"), icon: , }, { key: "early-access", - label: t("settings.sections.early-access"), + label: t("portal.settings.sections.early-access"), icon: , }, ], @@ -231,7 +231,9 @@ export function SettingsModal({ value: r.value, label: r.enterpriseOnly && tier !== "enterprise" - ? t("settings.workspace.regionEnterpriseSuffix", { region: r.label }) + ? t("portal.settings.workspace.regionEnterpriseSuffix", { + region: r.label, + }) : r.label, disabled: r.enterpriseOnly && tier !== "enterprise", })); @@ -244,25 +246,25 @@ export function SettingsModal({ open={open} onClose={onClose} width="xl" - ariaLabel={t("settings.ariaLabel")} + ariaLabel={t("portal.settings.ariaLabel")} className="portal-settings" > setSection(k as SettingsSection)} - title={t(`settings.sections.${section}`)} + title={t(`portal.settings.sections.${section}`)} onClose={onClose} footer={ <> - {t("settings.footerNote")} + {t("portal.settings.footerNote")} } @@ -384,13 +386,13 @@ function ProfilePanel({
- {name || t("settings.profile.accountFallback")} + {name || t("portal.settings.profile.accountFallback")} {role && ( {role} @@ -400,27 +402,27 @@ function ProfilePanel({ {email}
- + onName(e.target.value)} - placeholder={t("settings.profile.namePlaceholder")} + placeholder={t("portal.settings.profile.namePlaceholder")} /> onEmail(e.target.value)} - placeholder={t("settings.profile.emailPlaceholder")} + placeholder={t("portal.settings.profile.emailPlaceholder")} />
@@ -444,16 +446,16 @@ function AppearancePanel({

- {t("settings.appearance.themeTitle")} + {t("portal.settings.appearance.themeTitle")}

- {t("settings.appearance.themeSub")} + {t("portal.settings.appearance.themeSub")}

{THEME_OPTIONS.map((opt) => ( ))} @@ -507,10 +511,10 @@ function NotificationsPanel({

- {t("settings.notifications.title")} + {t("portal.settings.notifications.title")}

- {t("settings.notifications.sub")} + {t("portal.settings.notifications.sub")}

@@ -537,8 +541,12 @@ function NotificationsPanel({ return (
- {t(`settings.notifications.${id}.label`)} - {t(`settings.notifications.${id}.description`)} + + {t(`portal.settings.notifications.${id}.label`)} + + + {t(`portal.settings.notifications.${id}.description`)} +
- + onWorkspaceName(e.target.value)} - placeholder={t("settings.workspace.namePlaceholder")} + placeholder={t("portal.settings.workspace.namePlaceholder")} /> ({ value, - label: t(`settings.authentication.timeout.${value}`), + label: t(`portal.settings.authentication.timeout.${value}`), }))} /> @@ -778,10 +790,10 @@ function SessionsPanel({

- {t("settings.sessions.title")} + {t("portal.settings.sessions.title")}

- {t("settings.sessions.sub")} + {t("portal.settings.sessions.sub")}

@@ -795,12 +807,12 @@ function SessionsPanel({
{s.current ? ( - {t("settings.sessions.thisDevice")} + {t("portal.settings.sessions.thisDevice")} ) : ( // TODO(backend): DELETE /v1/settings/sessions/{id} )}
@@ -845,10 +857,10 @@ function EarlyAccessPanel({

- {t("settings.earlyAccess.title")} + {t("portal.settings.earlyAccess.title")}

- {t("settings.earlyAccess.sub")} + {t("portal.settings.earlyAccess.sub")}

@@ -861,7 +873,7 @@ function EarlyAccessPanel({ {f.label} {locked && ( - {t("settings.enterpriseBadge")} + {t("portal.settings.enterpriseBadge")} )} diff --git a/frontend/portal/src/components/Sidebar.css b/frontend/editor/src/portal/components/Sidebar.css similarity index 100% rename from frontend/portal/src/components/Sidebar.css rename to frontend/editor/src/portal/components/Sidebar.css diff --git a/frontend/portal/src/components/Sidebar.stories.tsx b/frontend/editor/src/portal/components/Sidebar.stories.tsx similarity index 100% rename from frontend/portal/src/components/Sidebar.stories.tsx rename to frontend/editor/src/portal/components/Sidebar.stories.tsx diff --git a/frontend/portal/src/components/Sidebar.tsx b/frontend/editor/src/portal/components/Sidebar.tsx similarity index 88% rename from frontend/portal/src/components/Sidebar.tsx rename to frontend/editor/src/portal/components/Sidebar.tsx index ecfa0e7900..d9ced4ae65 100644 --- a/frontend/portal/src/components/Sidebar.tsx +++ b/frontend/editor/src/portal/components/Sidebar.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { Dropdown, NavItem } from "@shared/components"; +import { Dropdown, NavItem } from "@app/ui"; import { useView, type ViewId } from "@portal/contexts/ViewContext"; import { useTier } from "@portal/contexts/TierContext"; import { useTheme } from "@portal/contexts/ThemeContext"; @@ -8,8 +8,8 @@ import { useLink } from "@portal/contexts/LinkContext"; import { useAsync } from "@portal/hooks/useAsync"; import { fetchHomeKpis, type KpiEntry } from "@portal/api/home"; import { EDITOR_URL } from "@portal/auth/editorUrl"; -import markLight from "@shared/assets/brand/modern-logo/StirlingPDFLogoNoTextLight.svg"; -import markDark from "@shared/assets/brand/modern-logo/StirlingPDFLogoNoTextDark.svg"; +import markLight from "@app/assets/brand/modern-logo/StirlingPDFLogoNoTextLight.svg"; +import markDark from "@app/assets/brand/modern-logo/StirlingPDFLogoNoTextDark.svg"; import { HomeIcon, UsersIcon, @@ -64,7 +64,7 @@ function LinkAccountFooterItem() { return ( } onClick={() => openLinkModal()} /> @@ -93,7 +93,7 @@ function UsageFooter() {
- {t("shell.sidebar.docsProcessed")} + {t("portal.shell.sidebar.docsProcessed")} {docs ?? "—"}
@@ -114,8 +114,8 @@ function UsageFooter() { const planLabel = tier === "pro" - ? t("shell.sidebar.planProcessor", "Processor plan") - : t("shell.sidebar.planEnterprise", "Enterprise plan"); + ? t("portal.shell.sidebar.planProcessor", "Processor plan") + : t("portal.shell.sidebar.planEnterprise", "Enterprise plan"); return (
@@ -125,7 +125,7 @@ function UsageFooter() { {planLabel} - {docs != null ? t("shell.sidebar.docsCount", { docs }) : "—"} + {docs != null ? t("portal.shell.sidebar.docsCount", { docs }) : "—"}
@@ -151,7 +151,7 @@ export function Sidebar() { setActiveView(id as ViewId)} @@ -162,7 +162,7 @@ export function Sidebar() { return (
- {t("opRunner.featuredOps")} + {t("portal.opRunner.featuredOps")}
{opsIsLoading && @@ -270,8 +264,8 @@ export function SingleOpRunner({ {opsIsEmpty && ( )} {ops?.map((op) => ( @@ -307,18 +301,18 @@ export function SingleOpRunner({ {phase === "idle" && selectedOp && (
- {t("opRunner.hint.ready")} + {t("portal.opRunner.hint.ready")}

- {t("opRunner.hint.runOn.before")}{" "} + {t("portal.opRunner.hint.runOn.before")}{" "} {selectedOp.label}{" "} - {t("opRunner.hint.runOn.middle")}{" "} - {sample ?? t("opRunner.hint.aSample")} + {t("portal.opRunner.hint.runOn.middle")}{" "} + {sample ?? t("portal.opRunner.hint.aSample")}

- {selectedOp.blurb}. {t("opRunner.hint.press")}{" "} - {t("opRunner.action.run")}{" "} - {t("opRunner.hint.toInvoke")}{" "} + {selectedOp.blurb}. {t("portal.opRunner.hint.press")}{" "} + {t("portal.opRunner.action.run")}{" "} + {t("portal.opRunner.hint.toInvoke")}{" "} POST {selectedOp.endpoint}.

@@ -328,7 +322,9 @@ export function SingleOpRunner({
- {t("opRunner.running.title", { label: selectedOp.label })} + {t("portal.opRunner.running.title", { + label: selectedOp.label, + })}
POST {selectedOp.endpoint}
@@ -338,11 +334,13 @@ export function SingleOpRunner({
- {t("opRunner.status.completed")} + {t("portal.opRunner.status.completed")} POST {selectedOp.endpoint} - {t("opRunner.durationMs", { ms: runResult.durationMs })} + {t("portal.opRunner.durationMs", { + ms: runResult.durationMs, + })}
@@ -356,10 +354,10 @@ export function SingleOpRunner({
                 className="portal-runner__hint-eyebrow"
                 style={{ color: "var(--color-red)" }}
               >
-                {t("opRunner.status.failed")}
+                {t("portal.opRunner.status.failed")}
               
-

{t("opRunner.error.title")}

-

{errorMsg ?? t("opRunner.error.unknown")}

+

{t("portal.opRunner.error.title")}

+

{errorMsg ?? t("portal.opRunner.error.unknown")}

)}
diff --git a/frontend/portal/src/components/UsageAreaChart.css b/frontend/editor/src/portal/components/UsageAreaChart.css similarity index 100% rename from frontend/portal/src/components/UsageAreaChart.css rename to frontend/editor/src/portal/components/UsageAreaChart.css diff --git a/frontend/portal/src/components/UsageAreaChart.stories.tsx b/frontend/editor/src/portal/components/UsageAreaChart.stories.tsx similarity index 100% rename from frontend/portal/src/components/UsageAreaChart.stories.tsx rename to frontend/editor/src/portal/components/UsageAreaChart.stories.tsx diff --git a/frontend/portal/src/components/UsageAreaChart.tsx b/frontend/editor/src/portal/components/UsageAreaChart.tsx similarity index 97% rename from frontend/portal/src/components/UsageAreaChart.tsx rename to frontend/editor/src/portal/components/UsageAreaChart.tsx index 65fd0e3eec..ff9750d57c 100644 --- a/frontend/portal/src/components/UsageAreaChart.tsx +++ b/frontend/editor/src/portal/components/UsageAreaChart.tsx @@ -35,7 +35,7 @@ export function UsageAreaChart({ deltaPct, }: UsageAreaChartProps) { const { t } = useTranslation(); - const totalLabel = totalLabelProp ?? t("usageChart.defaultLabel"); + const totalLabel = totalLabelProp ?? t("portal.usageChart.defaultLabel"); const [hoverIndex, setHoverIndex] = useState(null); const svgRef = useRef(null); @@ -153,7 +153,7 @@ export function UsageAreaChart({ } > {deltaPct >= 0 ? "↑" : "↓"} - {t("usageChart.delta", { + {t("portal.usageChart.delta", { pct: Math.abs(Math.round(deltaPct * 100)), })}
@@ -265,7 +265,7 @@ export function UsageAreaChart({ })}
- {t("usageChart.docsValue", { + {t("portal.usageChart.docsValue", { value: hovered.raw.value.toLocaleString(), })}
@@ -273,7 +273,7 @@ export function UsageAreaChart({ )}
{hovered - ? t("usageChart.srAnnounce", { + ? t("portal.usageChart.srAnnounce", { date: new Date(hovered.raw.date).toLocaleDateString(undefined, { weekday: "short", month: "short", diff --git a/frontend/portal/src/components/WelcomeCarousel.css b/frontend/editor/src/portal/components/WelcomeCarousel.css similarity index 100% rename from frontend/portal/src/components/WelcomeCarousel.css rename to frontend/editor/src/portal/components/WelcomeCarousel.css diff --git a/frontend/portal/src/components/WelcomeCarousel.stories.tsx b/frontend/editor/src/portal/components/WelcomeCarousel.stories.tsx similarity index 100% rename from frontend/portal/src/components/WelcomeCarousel.stories.tsx rename to frontend/editor/src/portal/components/WelcomeCarousel.stories.tsx diff --git a/frontend/portal/src/components/WelcomeCarousel.tsx b/frontend/editor/src/portal/components/WelcomeCarousel.tsx similarity index 82% rename from frontend/portal/src/components/WelcomeCarousel.tsx rename to frontend/editor/src/portal/components/WelcomeCarousel.tsx index b485eee1a4..d0b01708e6 100644 --- a/frontend/portal/src/components/WelcomeCarousel.tsx +++ b/frontend/editor/src/portal/components/WelcomeCarousel.tsx @@ -1,6 +1,6 @@ import { useEffect, useState, type ReactNode } from "react"; import { useTranslation } from "react-i18next"; -import { Button, StatusBadge } from "@shared/components"; +import { Button, StatusBadge } from "@app/ui"; import { useView, type ViewId } from "@portal/contexts/ViewContext"; import "@portal/components/WelcomeCarousel.css"; @@ -21,18 +21,18 @@ function EditorOrnament() { return (
- {t("welcome.ornament.editor.critical")} + {t("portal.welcome.ornament.editor.critical")}
Vulnerability Assessment Report
CVE-2026-1847 · 12 pages
- {t("welcome.ornament.editor.signed")} + {t("portal.welcome.ornament.editor.signed")} · - {t("welcome.ornament.editor.ocrClean")} + {t("portal.welcome.ornament.editor.ocrClean")} · - {t("welcome.ornament.editor.schemaMatch")} + {t("portal.welcome.ornament.editor.schemaMatch")}
); @@ -88,9 +88,12 @@ const SLIDES: Slide[] = [ { id: "editor", durationMs: 12000, - primary: { labelKey: "welcome.slides.editor.primary", target: "editor" }, + primary: { + labelKey: "portal.welcome.slides.editor.primary", + target: "editor", + }, secondary: { - labelKey: "welcome.slides.editor.secondary", + labelKey: "portal.welcome.slides.editor.secondary", target: "editor", }, ornament: , @@ -98,9 +101,12 @@ const SLIDES: Slide[] = [ { id: "platform", durationMs: 8000, - primary: { labelKey: "welcome.slides.platform.primary", action: "try-op" }, + primary: { + labelKey: "portal.welcome.slides.platform.primary", + action: "try-op", + }, secondary: { - labelKey: "welcome.slides.platform.secondary", + labelKey: "portal.welcome.slides.platform.secondary", target: "infrastructure", }, ornament: , @@ -108,8 +114,14 @@ const SLIDES: Slide[] = [ { id: "agents", durationMs: 8000, - primary: { labelKey: "welcome.slides.agents.primary", target: "sources" }, - secondary: { labelKey: "welcome.slides.agents.secondary", target: "docs" }, + primary: { + labelKey: "portal.welcome.slides.agents.primary", + target: "sources", + }, + secondary: { + labelKey: "portal.welcome.slides.agents.secondary", + target: "docs", + }, ornament: , }, ]; @@ -157,19 +169,19 @@ export function WelcomeCarousel({ onTryOp }: WelcomeCarouselProps) { setPaused(false); } }} - aria-label={t("welcome.ariaLabel")} + aria-label={t("portal.welcome.ariaLabel")} aria-roledescription="carousel" >
- {t(`welcome.slides.${slide.id}.eyebrow`)} + {t(`portal.welcome.slides.${slide.id}.eyebrow`)}

- {t(`welcome.slides.${slide.id}.title`)} + {t(`portal.welcome.slides.${slide.id}.title`)}

- {t(`welcome.slides.${slide.id}.sub`)} + {t(`portal.welcome.slides.${slide.id}.sub`)}

) : (
)} diff --git a/frontend/portal/src/components/account-link/LinkAccountModal.tsx b/frontend/editor/src/portal/components/account-link/LinkAccountModal.tsx similarity index 76% rename from frontend/portal/src/components/account-link/LinkAccountModal.tsx rename to frontend/editor/src/portal/components/account-link/LinkAccountModal.tsx index 8c23bbcd0f..2b07e12b3a 100644 --- a/frontend/portal/src/components/account-link/LinkAccountModal.tsx +++ b/frontend/editor/src/portal/components/account-link/LinkAccountModal.tsx @@ -1,12 +1,12 @@ import { useEffect } from "react"; import { useTranslation } from "react-i18next"; -import { Banner, Button, Modal } from "@shared/components"; -import SupabaseLoginForm from "@shared/auth/ui/SupabaseLoginForm"; +import { Banner, Button, Modal } from "@app/ui"; +import SupabaseLoginForm from "@app/auth/ui/SupabaseLoginForm"; import { useSupabaseLogin, type SupabaseLoginSession, -} from "@shared/auth/ui/useSupabaseLogin"; -import "@shared/auth/ui/auth-theme.css"; +} from "@app/auth/ui/useSupabaseLogin"; +import "@app/auth/ui/auth-theme.css"; import { ensureSaasSupabase, isSaasSupabaseConfigured, @@ -66,17 +66,20 @@ export function LinkAccountModal({ width="md" title={ reauth - ? t("accountLink.modal.reauthTitle", "Sign in again") - : t("accountLink.modal.linkTitle", "Link your Stirling account") + ? t("portal.accountLink.modal.reauthTitle", "Sign in again") + : t( + "portal.accountLink.modal.linkTitle", + "Link your Stirling account", + ) } subtitle={ reauth ? t( - "accountLink.modal.reauthSubtitle", + "portal.accountLink.modal.reauthSubtitle", "Your session expired — sign back in to your Stirling account. Your instance stays linked.", ) : t( - "accountLink.modal.linkSubtitle", + "portal.accountLink.modal.linkSubtitle", "Sign in to the account this server should bill against.", ) } @@ -88,16 +91,16 @@ export function LinkAccountModal({ - {t("accountLink.modal.loginNotConfigured.before", "Set")}{" "} + {t("portal.accountLink.modal.loginNotConfigured.before", "Set")}{" "} VITE_SAAS_SUPABASE_URL{" "} - {t("accountLink.modal.loginNotConfigured.and", "and")}{" "} + {t("portal.accountLink.modal.loginNotConfigured.and", "and")}{" "} VITE_SAAS_SUPABASE_ANON_KEY{" "} {t( - "accountLink.modal.loginNotConfigured.after", + "portal.accountLink.modal.loginNotConfigured.after", "to enable in-app linking against the hosted Stirling account.", )} @@ -109,7 +112,10 @@ export function LinkAccountModal({ onClose(); }} > - {t("accountLink.modal.simulateSignIn", "Simulate sign-in (dev)")} + {t( + "portal.accountLink.modal.simulateSignIn", + "Simulate sign-in (dev)", + )} )}
diff --git a/frontend/portal/src/components/account-link/LinkGate.stories.tsx b/frontend/editor/src/portal/components/account-link/LinkGate.stories.tsx similarity index 93% rename from frontend/portal/src/components/account-link/LinkGate.stories.tsx rename to frontend/editor/src/portal/components/account-link/LinkGate.stories.tsx index af2ac5c96b..60d9a9962c 100644 --- a/frontend/portal/src/components/account-link/LinkGate.stories.tsx +++ b/frontend/editor/src/portal/components/account-link/LinkGate.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { Card } from "@shared/components"; +import { Card } from "@app/ui"; import { LinkGate } from "@portal/components/account-link/LinkGate"; const meta: Meta = { diff --git a/frontend/portal/src/components/account-link/LinkGate.tsx b/frontend/editor/src/portal/components/account-link/LinkGate.tsx similarity index 74% rename from frontend/portal/src/components/account-link/LinkGate.tsx rename to frontend/editor/src/portal/components/account-link/LinkGate.tsx index de56c844e9..d5743d0041 100644 --- a/frontend/portal/src/components/account-link/LinkGate.tsx +++ b/frontend/editor/src/portal/components/account-link/LinkGate.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from "react"; import { useTranslation } from "react-i18next"; -import { Banner, Button } from "@shared/components"; +import { Banner, Button } from "@app/ui"; import { useLink } from "@portal/contexts/LinkContext"; import { useUI } from "@portal/contexts/UIContext"; @@ -29,18 +29,22 @@ export function LinkGate({ children, feature }: Props) { tone="info" title={ feature - ? t("accountLink.gate.titleFeature", "Link to unlock {{feature}}", { - feature, - }) - : t("accountLink.gate.title", "Link to unlock") + ? t( + "portal.accountLink.gate.titleFeature", + "Link to unlock {{feature}}", + { + feature, + }, + ) + : t("portal.accountLink.gate.title", "Link to unlock") } description={t( - "accountLink.gate.description", + "portal.accountLink.gate.description", "Link this org's Stirling account to use billable features.", )} action={ } /> diff --git a/frontend/portal/src/components/account-link/LinkedInstancesTable.stories.tsx b/frontend/editor/src/portal/components/account-link/LinkedInstancesTable.stories.tsx similarity index 100% rename from frontend/portal/src/components/account-link/LinkedInstancesTable.stories.tsx rename to frontend/editor/src/portal/components/account-link/LinkedInstancesTable.stories.tsx diff --git a/frontend/portal/src/components/account-link/LinkedInstancesTable.tsx b/frontend/editor/src/portal/components/account-link/LinkedInstancesTable.tsx similarity index 69% rename from frontend/portal/src/components/account-link/LinkedInstancesTable.tsx rename to frontend/editor/src/portal/components/account-link/LinkedInstancesTable.tsx index a6460016d5..f6839596d3 100644 --- a/frontend/portal/src/components/account-link/LinkedInstancesTable.tsx +++ b/frontend/editor/src/portal/components/account-link/LinkedInstancesTable.tsx @@ -7,7 +7,7 @@ import { StatusBadge, Table, type TableColumn, -} from "@shared/components"; +} from "@app/ui"; import type { LinkedInstanceRow } from "@portal/api/link"; interface Props { @@ -19,20 +19,21 @@ interface Props { } function relativeTime(iso: string | null, t: TFunction): string { - if (!iso) return t("accountLink.instances.time.never", "never"); + if (!iso) return t("portal.accountLink.instances.time.never", "never"); const diffMs = Date.now() - new Date(iso).getTime(); const mins = Math.round(diffMs / 60_000); - if (mins < 1) return t("accountLink.instances.time.justNow", "just now"); + if (mins < 1) + return t("portal.accountLink.instances.time.justNow", "just now"); if (mins < 60) - return t("accountLink.instances.time.minutesAgo", "{{count}}m ago", { + return t("portal.accountLink.instances.time.minutesAgo", "{{count}}m ago", { count: mins, }); const hrs = Math.round(mins / 60); if (hrs < 24) - return t("accountLink.instances.time.hoursAgo", "{{count}}h ago", { + return t("portal.accountLink.instances.time.hoursAgo", "{{count}}h ago", { count: hrs, }); - return t("accountLink.instances.time.daysAgo", "{{count}}d ago", { + return t("portal.accountLink.instances.time.daysAgo", "{{count}}d ago", { count: Math.round(hrs / 24), }); } @@ -47,11 +48,12 @@ export function LinkedInstancesTable({ const cols: TableColumn[] = [ { key: "name", - header: t("accountLink.instances.columns.instance", "Instance"), + header: t("portal.accountLink.instances.columns.instance", "Instance"), render: (i) => (
- {i.name ?? t("accountLink.instances.unnamed", "Unnamed instance")} + {i.name ?? + t("portal.accountLink.instances.unnamed", "Unnamed instance")} {i.deviceId}
@@ -59,21 +61,21 @@ export function LinkedInstancesTable({ }, { key: "status", - header: t("accountLink.instances.columns.status", "Status"), + header: t("portal.accountLink.instances.columns.status", "Status"), render: (i) => i.revoked ? ( - {t("accountLink.instances.revoked", "Revoked")} + {t("portal.accountLink.instances.revoked", "Revoked")} ) : ( - {t("accountLink.instances.active", "Active")} + {t("portal.accountLink.instances.active", "Active")} ), }, { key: "lastSeen", - header: t("accountLink.instances.columns.lastSeen", "Last seen"), + header: t("portal.accountLink.instances.columns.lastSeen", "Last seen"), render: (i) => ( {relativeTime(i.lastSeenAt, t)} @@ -82,7 +84,7 @@ export function LinkedInstancesTable({ }, { key: "created", - header: t("accountLink.instances.columns.linked", "Linked"), + header: t("portal.accountLink.instances.columns.linked", "Linked"), render: (i) => ( {relativeTime(i.createdAt, t)} @@ -102,7 +104,7 @@ export function LinkedInstancesTable({ loading={revokingId === i.instanceId} onClick={() => onRevoke(i)} > - {t("accountLink.instances.revoke", "Revoke")} + {t("portal.accountLink.instances.revoke", "Revoke")} ), }, @@ -113,9 +115,12 @@ export function LinkedInstancesTable({ {instances.length === 0 ? ( diff --git a/frontend/portal/src/components/agent-builder/AgentBuilderPanel.stories.tsx b/frontend/editor/src/portal/components/agent-builder/AgentBuilderPanel.stories.tsx similarity index 100% rename from frontend/portal/src/components/agent-builder/AgentBuilderPanel.stories.tsx rename to frontend/editor/src/portal/components/agent-builder/AgentBuilderPanel.stories.tsx diff --git a/frontend/portal/src/components/agent-builder/AgentBuilderPanel.tsx b/frontend/editor/src/portal/components/agent-builder/AgentBuilderPanel.tsx similarity index 87% rename from frontend/portal/src/components/agent-builder/AgentBuilderPanel.tsx rename to frontend/editor/src/portal/components/agent-builder/AgentBuilderPanel.tsx index 46b45231b2..20cbcab89c 100644 --- a/frontend/portal/src/components/agent-builder/AgentBuilderPanel.tsx +++ b/frontend/editor/src/portal/components/agent-builder/AgentBuilderPanel.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { StatusBadge, Tabs, type TabItem } from "@shared/components"; +import { StatusBadge, Tabs, type TabItem } from "@app/ui"; import { type Agent, AGENT_STATUS_TONE } from "@portal/api/agents"; import { ScenariosPanel } from "@portal/components/agent-builder/ScenariosPanel"; import { ToolsPanel } from "@portal/components/agent-builder/ToolsPanel"; @@ -27,18 +27,18 @@ export function AgentBuilderPanel({ const tabs: TabItem[] = [ { key: "scenarios", - label: t("agentBuilder.tabs.scenarios"), + label: t("portal.agentBuilder.tabs.scenarios"), count: agent.scenarios.length, }, - { key: "tools", label: t("agentBuilder.tabs.tools") }, + { key: "tools", label: t("portal.agentBuilder.tabs.tools") }, { key: "evals", - label: t("agentBuilder.tabs.evals"), + label: t("portal.agentBuilder.tabs.evals"), count: agent.evalsTotal > 0 ? agent.evalsTotal : undefined, }, { key: "versions", - label: t("agentBuilder.tabs.versions"), + label: t("portal.agentBuilder.tabs.versions"), count: agent.versions.length, }, ]; @@ -66,7 +66,7 @@ export function AgentBuilderPanel({ activeKey={tab} onChange={setTab} variant="underline" - ariaLabel={t("agentBuilder.sectionsAriaLabel")} + ariaLabel={t("portal.agentBuilder.sectionsAriaLabel")} /> {tab === "scenarios" && } diff --git a/frontend/portal/src/components/agent-builder/AgentKpiStrip.stories.tsx b/frontend/editor/src/portal/components/agent-builder/AgentKpiStrip.stories.tsx similarity index 100% rename from frontend/portal/src/components/agent-builder/AgentKpiStrip.stories.tsx rename to frontend/editor/src/portal/components/agent-builder/AgentKpiStrip.stories.tsx diff --git a/frontend/portal/src/components/agent-builder/AgentKpiStrip.tsx b/frontend/editor/src/portal/components/agent-builder/AgentKpiStrip.tsx similarity index 70% rename from frontend/portal/src/components/agent-builder/AgentKpiStrip.tsx rename to frontend/editor/src/portal/components/agent-builder/AgentKpiStrip.tsx index 029d300f29..0d3b729920 100644 --- a/frontend/portal/src/components/agent-builder/AgentKpiStrip.tsx +++ b/frontend/editor/src/portal/components/agent-builder/AgentKpiStrip.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { MetricCard, MetricStrip } from "@shared/components"; +import { MetricCard, MetricStrip } from "@app/ui"; import type { AgentsSummary } from "@portal/api/agents"; interface AgentKpiStripProps { @@ -16,10 +16,10 @@ export function AgentKpiStrip({ summary, loading }: AgentKpiStripProps) { * states; only values flow from the API. */ const kpiLabels = [ - t("agentBuilder.kpi.activeAgents"), - t("agentBuilder.kpi.avgPassRate"), - t("agentBuilder.kpi.scenarios"), - t("agentBuilder.kpi.latestPublished"), + t("portal.agentBuilder.kpi.activeAgents"), + t("portal.agentBuilder.kpi.avgPassRate"), + t("portal.agentBuilder.kpi.scenarios"), + t("portal.agentBuilder.kpi.latestPublished"), ] as const; const values: (string | number)[] = summary @@ -33,10 +33,12 @@ export function AgentKpiStrip({ summary, loading }: AgentKpiStripProps) { const descriptions: (string | undefined)[] = summary ? [ - t("agentBuilder.kpi.totalDescription", { count: summary.totalAgents }), - t("agentBuilder.kpi.acrossGoldenSets"), - t("agentBuilder.kpi.testCases"), - t("agentBuilder.kpi.fleetWide"), + t("portal.agentBuilder.kpi.totalDescription", { + count: summary.totalAgents, + }), + t("portal.agentBuilder.kpi.acrossGoldenSets"), + t("portal.agentBuilder.kpi.testCases"), + t("portal.agentBuilder.kpi.fleetWide"), ] : []; diff --git a/frontend/portal/src/components/agent-builder/AgentSelector.stories.tsx b/frontend/editor/src/portal/components/agent-builder/AgentSelector.stories.tsx similarity index 100% rename from frontend/portal/src/components/agent-builder/AgentSelector.stories.tsx rename to frontend/editor/src/portal/components/agent-builder/AgentSelector.stories.tsx diff --git a/frontend/portal/src/components/agent-builder/AgentSelector.tsx b/frontend/editor/src/portal/components/agent-builder/AgentSelector.tsx similarity index 93% rename from frontend/portal/src/components/agent-builder/AgentSelector.tsx rename to frontend/editor/src/portal/components/agent-builder/AgentSelector.tsx index 73248b8128..f6734721b8 100644 --- a/frontend/portal/src/components/agent-builder/AgentSelector.tsx +++ b/frontend/editor/src/portal/components/agent-builder/AgentSelector.tsx @@ -1,6 +1,6 @@ import { useTranslation } from "react-i18next"; import { type Agent, AGENT_STATUS_TONE } from "@portal/api/agents"; -import { StatusBadge } from "@shared/components"; +import { StatusBadge } from "@app/ui"; import "@portal/views/AgentBuilder.css"; interface AgentSelectorProps { @@ -19,7 +19,7 @@ export function AgentSelector({ return (
} >

- {t("agentBuilder.bootstrap.lead")} + {t("portal.agentBuilder.bootstrap.lead")}

diff --git a/frontend/portal/src/components/agent-builder/EvalsPanel.stories.tsx b/frontend/editor/src/portal/components/agent-builder/EvalsPanel.stories.tsx similarity index 100% rename from frontend/portal/src/components/agent-builder/EvalsPanel.stories.tsx rename to frontend/editor/src/portal/components/agent-builder/EvalsPanel.stories.tsx diff --git a/frontend/portal/src/components/agent-builder/EvalsPanel.tsx b/frontend/editor/src/portal/components/agent-builder/EvalsPanel.tsx similarity index 73% rename from frontend/portal/src/components/agent-builder/EvalsPanel.tsx rename to frontend/editor/src/portal/components/agent-builder/EvalsPanel.tsx index 651f6c1d69..3f1580652f 100644 --- a/frontend/portal/src/components/agent-builder/EvalsPanel.tsx +++ b/frontend/editor/src/portal/components/agent-builder/EvalsPanel.tsx @@ -7,7 +7,7 @@ import { StatusBadge, Table, type TableColumn, -} from "@shared/components"; +} from "@app/ui"; import type { Agent, EvalCase } from "@portal/api/agents"; import "@portal/views/AgentBuilder.css"; @@ -22,32 +22,32 @@ export function EvalsPanel({ agent }: EvalsPanelProps) { const columns: TableColumn[] = [ { key: "name", - header: t("agentBuilder.evals.columnCase"), + header: t("portal.agentBuilder.evals.columnCase"), render: (c) => c.name, }, { key: "result", - header: t("agentBuilder.evals.columnResult"), + header: t("portal.agentBuilder.evals.columnResult"), render: (c) => c.passing === null ? ( - {t("agentBuilder.evals.notRun")} + {t("portal.agentBuilder.evals.notRun")} ) : ( {c.passing - ? t("agentBuilder.evals.pass") - : t("agentBuilder.evals.fail")} + ? t("portal.agentBuilder.evals.pass") + : t("portal.agentBuilder.evals.fail")} ), }, { key: "latency", - header: t("agentBuilder.evals.columnLatency"), + header: t("portal.agentBuilder.evals.columnLatency"), align: "right", render: (c) => ( - {t("agentBuilder.evals.latencyMs", { ms: c.latencyMs })} + {t("portal.agentBuilder.evals.latencyMs", { ms: c.latencyMs })} ), }, @@ -57,8 +57,8 @@ export function EvalsPanel({ agent }: EvalsPanelProps) { return (
@@ -77,29 +77,29 @@ export function EvalsPanel({ agent }: EvalsPanelProps) {
= 0.95 ? "success" : rate >= 0.8 ? "warning" : "danger"} />
- {t("agentBuilder.evals.goldenSetPassRate")} + {t("portal.agentBuilder.evals.goldenSetPassRate")} {Math.round(rate * 100)}%
= 0.95 ? "var(--color-green)" : "var(--color-amber)"} - label={t("agentBuilder.evals.goldenSetPassRate")} + label={t("portal.agentBuilder.evals.goldenSetPassRate")} />
diff --git a/frontend/portal/src/components/agent-builder/ScenariosPanel.stories.tsx b/frontend/editor/src/portal/components/agent-builder/ScenariosPanel.stories.tsx similarity index 100% rename from frontend/portal/src/components/agent-builder/ScenariosPanel.stories.tsx rename to frontend/editor/src/portal/components/agent-builder/ScenariosPanel.stories.tsx diff --git a/frontend/portal/src/components/agent-builder/ScenariosPanel.tsx b/frontend/editor/src/portal/components/agent-builder/ScenariosPanel.tsx similarity index 79% rename from frontend/portal/src/components/agent-builder/ScenariosPanel.tsx rename to frontend/editor/src/portal/components/agent-builder/ScenariosPanel.tsx index f355dfa4d0..f4057ea2b6 100644 --- a/frontend/portal/src/components/agent-builder/ScenariosPanel.tsx +++ b/frontend/editor/src/portal/components/agent-builder/ScenariosPanel.tsx @@ -1,12 +1,6 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { - Button, - Chip, - FormField, - Input, - StatusBadge, -} from "@shared/components"; +import { Button, Chip, FormField, Input, StatusBadge } from "@app/ui"; import type { Agent, Scenario } from "@portal/api/agents"; import "@portal/views/AgentBuilder.css"; @@ -64,8 +58,8 @@ export function ScenariosPanel({ agent }: ScenariosPanelProps) { showDot={false} > {s.enabled - ? t("agentBuilder.scenarios.inEval") - : t("agentBuilder.scenarios.muted")} + ? t("portal.agentBuilder.scenarios.inEval") + : t("portal.agentBuilder.scenarios.muted")}
@@ -78,8 +72,8 @@ export function ScenariosPanel({ agent }: ScenariosPanelProps) { onClick={() => toggleEnabled(s.id)} > {s.enabled - ? t("agentBuilder.scenarios.mute") - : t("agentBuilder.scenarios.enable")} + ? t("portal.agentBuilder.scenarios.mute") + : t("portal.agentBuilder.scenarios.enable")} ))} @@ -87,25 +81,29 @@ export function ScenariosPanel({ agent }: ScenariosPanelProps) {
- {t("agentBuilder.scenarios.addScenario")} + {t("portal.agentBuilder.scenarios.addScenario")}
- + setName(e.target.value)} - placeholder={t("agentBuilder.scenarios.namePlaceholder")} + placeholder={t("portal.agentBuilder.scenarios.namePlaceholder")} /> - + setExpectation(e.target.value)} - placeholder={t("agentBuilder.scenarios.expectationPlaceholder")} + placeholder={t( + "portal.agentBuilder.scenarios.expectationPlaceholder", + )} />
diff --git a/frontend/portal/src/components/agent-builder/ToolsPanel.stories.tsx b/frontend/editor/src/portal/components/agent-builder/ToolsPanel.stories.tsx similarity index 100% rename from frontend/portal/src/components/agent-builder/ToolsPanel.stories.tsx rename to frontend/editor/src/portal/components/agent-builder/ToolsPanel.stories.tsx diff --git a/frontend/portal/src/components/agent-builder/ToolsPanel.tsx b/frontend/editor/src/portal/components/agent-builder/ToolsPanel.tsx similarity index 83% rename from frontend/portal/src/components/agent-builder/ToolsPanel.tsx rename to frontend/editor/src/portal/components/agent-builder/ToolsPanel.tsx index 81e0f57af4..4dde8ff54d 100644 --- a/frontend/portal/src/components/agent-builder/ToolsPanel.tsx +++ b/frontend/editor/src/portal/components/agent-builder/ToolsPanel.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { Chip, ToggleSwitch } from "@shared/components"; +import { Chip, ToggleSwitch } from "@app/ui"; import { type Agent, type ToolMode, TOOL_CATALOGUE } from "@portal/api/agents"; import "@portal/views/AgentBuilder.css"; @@ -40,27 +40,27 @@ export function ToolsPanel({ agent, governanceUnlocked }: ToolsPanelProps) { checked={restricted} onChange={setRestricted} disabled={!governanceUnlocked} - label={t("agentBuilder.tools.restrictedAccess")} + label={t("portal.agentBuilder.tools.restrictedAccess")} description={ governanceUnlocked - ? t("agentBuilder.tools.restrictedDescription") - : t("agentBuilder.tools.governanceGate") + ? t("portal.agentBuilder.tools.restrictedDescription") + : t("portal.agentBuilder.tools.governanceGate") } /> {restricted - ? t("agentBuilder.tools.restricted") - : t("agentBuilder.tools.broadAccess")} + ? t("portal.agentBuilder.tools.restricted") + : t("portal.agentBuilder.tools.broadAccess")} {restricted && (
- {t("agentBuilder.tools.deniedTools")} + {t("portal.agentBuilder.tools.deniedTools")}

- {t("agentBuilder.tools.deniedHint")} + {t("portal.agentBuilder.tools.deniedHint")}

{TOOL_CATALOGUE.map((tool) => { diff --git a/frontend/portal/src/components/agent-builder/VersionsPanel.stories.tsx b/frontend/editor/src/portal/components/agent-builder/VersionsPanel.stories.tsx similarity index 100% rename from frontend/portal/src/components/agent-builder/VersionsPanel.stories.tsx rename to frontend/editor/src/portal/components/agent-builder/VersionsPanel.stories.tsx diff --git a/frontend/portal/src/components/agent-builder/VersionsPanel.tsx b/frontend/editor/src/portal/components/agent-builder/VersionsPanel.tsx similarity index 91% rename from frontend/portal/src/components/agent-builder/VersionsPanel.tsx rename to frontend/editor/src/portal/components/agent-builder/VersionsPanel.tsx index b05e2fa7fc..88edc7993c 100644 --- a/frontend/portal/src/components/agent-builder/VersionsPanel.tsx +++ b/frontend/editor/src/portal/components/agent-builder/VersionsPanel.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { Button, StatusBadge } from "@shared/components"; +import { Button, StatusBadge } from "@app/ui"; import { type Agent, AGENT_STATUS_TONE } from "@portal/api/agents"; import "@portal/views/AgentBuilder.css"; @@ -56,7 +56,7 @@ export function VersionsPanel({ agent, historyUnlocked }: VersionsPanelProps) { {isCurrent && ( - {t("agentBuilder.versions.current")} + {t("portal.agentBuilder.versions.current")} )}
@@ -72,7 +72,7 @@ export function VersionsPanel({ agent, historyUnlocked }: VersionsPanelProps) { variant="outline" onClick={() => publish(v.version)} > - {t("agentBuilder.versions.publish")} + {t("portal.agentBuilder.versions.publish")} )} {v.status === "published" && !isCurrent && ( @@ -81,7 +81,7 @@ export function VersionsPanel({ agent, historyUnlocked }: VersionsPanelProps) { variant="ghost" onClick={() => rollback(v.version)} > - {t("agentBuilder.versions.rollBack")} + {t("portal.agentBuilder.versions.rollBack")} )}
@@ -92,7 +92,7 @@ export function VersionsPanel({ agent, historyUnlocked }: VersionsPanelProps) { {!historyUnlocked && publishedExists && (

- {t("agentBuilder.versions.historyGate")} + {t("portal.agentBuilder.versions.historyGate")}

)} diff --git a/frontend/portal/src/components/billing/EnterpriseUpsell.stories.tsx b/frontend/editor/src/portal/components/billing/EnterpriseUpsell.stories.tsx similarity index 100% rename from frontend/portal/src/components/billing/EnterpriseUpsell.stories.tsx rename to frontend/editor/src/portal/components/billing/EnterpriseUpsell.stories.tsx diff --git a/frontend/portal/src/components/billing/EnterpriseUpsell.tsx b/frontend/editor/src/portal/components/billing/EnterpriseUpsell.tsx similarity index 74% rename from frontend/portal/src/components/billing/EnterpriseUpsell.tsx rename to frontend/editor/src/portal/components/billing/EnterpriseUpsell.tsx index 9ad3cbeae7..b303c530ff 100644 --- a/frontend/portal/src/components/billing/EnterpriseUpsell.tsx +++ b/frontend/editor/src/portal/components/billing/EnterpriseUpsell.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { Button, Card } from "@shared/components"; +import { Button, Card } from "@app/ui"; interface Props { /** Render without the Card wrapper, to embed inside another card's column. */ @@ -15,23 +15,29 @@ export function EnterpriseUpsell({ bare = false }: Props) { const body = ( <> - {t("billing.enterpriseUpsell.eyebrow", "Volume discount · 1M+ PDFs")} + {t( + "portal.billing.enterpriseUpsell.eyebrow", + "Volume discount · 1M+ PDFs", + )}

- {t("billing.enterpriseUpsell.title", "Stirling Enterprise")} + {t("portal.billing.enterpriseUpsell.title", "Stirling Enterprise")}

{t( - "billing.enterpriseUpsell.description", + "portal.billing.enterpriseUpsell.description", "Committed volume discounts, air-gapped deployment, custom MSA and security reviews, and 3rd-party distributor partnerships.", )}

{/* Destination wired when the enterprise/sales URL is confirmed. */}
diff --git a/frontend/portal/src/components/billing/FreePdfEditorsCard.stories.tsx b/frontend/editor/src/portal/components/billing/FreePdfEditorsCard.stories.tsx similarity index 100% rename from frontend/portal/src/components/billing/FreePdfEditorsCard.stories.tsx rename to frontend/editor/src/portal/components/billing/FreePdfEditorsCard.stories.tsx diff --git a/frontend/portal/src/components/billing/FreePdfEditorsCard.tsx b/frontend/editor/src/portal/components/billing/FreePdfEditorsCard.tsx similarity index 75% rename from frontend/portal/src/components/billing/FreePdfEditorsCard.tsx rename to frontend/editor/src/portal/components/billing/FreePdfEditorsCard.tsx index 2744e26d08..77cbdbad5b 100644 --- a/frontend/portal/src/components/billing/FreePdfEditorsCard.tsx +++ b/frontend/editor/src/portal/components/billing/FreePdfEditorsCard.tsx @@ -1,12 +1,6 @@ import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; -import { - Button, - Card, - MetricCard, - MetricStrip, - StatusBadge, -} from "@shared/components"; +import { Button, Card, MetricCard, MetricStrip, StatusBadge } from "@app/ui"; import GroupsIcon from "@mui/icons-material/GroupsRounded"; import PersonAddIcon from "@mui/icons-material/PersonAddAltRounded"; @@ -36,14 +30,17 @@ export function FreePdfEditorsCard() {

- {t("billing.freeEditors.title", "Free PDF Editors")}{" "} + {t("portal.billing.freeEditors.title", "Free PDF Editors")}{" "} - {t("billing.freeEditors.previewBadge", "Preview · sample data")} + {t( + "portal.billing.freeEditors.previewBadge", + "Preview · sample data", + )}

{t( - "billing.freeEditors.subtitle", + "portal.billing.freeEditors.subtitle", "Deploy anywhere, for your whole team.", )}

@@ -51,22 +48,25 @@ export function FreePdfEditorsCard() {
@@ -77,7 +77,7 @@ export function FreePdfEditorsCard() { leadingIcon={} onClick={() => navigate("/users?invite=1")} > - {t("billing.freeEditors.inviteTeammates", "Invite teammates")} + {t("portal.billing.freeEditors.inviteTeammates", "Invite teammates")} diff --git a/frontend/portal/src/components/billing/FreePlanView.stories.tsx b/frontend/editor/src/portal/components/billing/FreePlanView.stories.tsx similarity index 100% rename from frontend/portal/src/components/billing/FreePlanView.stories.tsx rename to frontend/editor/src/portal/components/billing/FreePlanView.stories.tsx diff --git a/frontend/portal/src/components/billing/FreePlanView.tsx b/frontend/editor/src/portal/components/billing/FreePlanView.tsx similarity index 83% rename from frontend/portal/src/components/billing/FreePlanView.tsx rename to frontend/editor/src/portal/components/billing/FreePlanView.tsx index 02a5352a43..0a1ae5216a 100644 --- a/frontend/portal/src/components/billing/FreePlanView.tsx +++ b/frontend/editor/src/portal/components/billing/FreePlanView.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { Banner, Button, StatusBadge } from "@shared/components"; +import { Banner, Button, StatusBadge } from "@app/ui"; import type { Wallet } from "@portal/api/billing"; import type { SaasCurrency } from "@portal/billing/stripe"; import { WalletMeter } from "@portal/components/billing/WalletMeter"; @@ -37,7 +37,7 @@ export function FreePlanView({ wallet, onSubscribed }: Props) { if (wallet.teamId == null) { setMissingTeam( t( - "billing.freePlan.noTeamResolved", + "portal.billing.freePlan.noTeamResolved", "No team is resolved on your wallet yet — refresh and try again.", ), ); @@ -53,7 +53,10 @@ export function FreePlanView({ wallet, onSubscribed }: Props) { onClick={openCheckout} disabled={wallet.teamId == null} > - {t("billing.freePlan.switchOnProcessor", "Switch on the Processor →")} + {t( + "portal.billing.freePlan.switchOnProcessor", + "Switch on the Processor →", + )} ) : null; @@ -62,20 +65,20 @@ export function FreePlanView({ wallet, onSubscribed }: Props) { {/* Current plan */}
- {t("billing.freePlan.currentPlan", "Current plan")} + {t("portal.billing.freePlan.currentPlan", "Current plan")}

- {t("billing.freePlan.planName", "Editor")} + {t("portal.billing.freePlan.planName", "Editor")}

- {t("billing.freePlan.freeForever", "Free forever")} + {t("portal.billing.freePlan.freeForever", "Free forever")} - {t("billing.freePlan.ssoIncluded", "SSO included")} + {t("portal.billing.freePlan.ssoIncluded", "SSO included")} - {t("billing.freePlan.unlimitedUsers", "Unlimited users")} + {t("portal.billing.freePlan.unlimitedUsers", "Unlimited users")}
@@ -89,7 +92,7 @@ export function FreePlanView({ wallet, onSubscribed }: Props) { @@ -99,7 +102,7 @@ export function FreePlanView({ wallet, onSubscribed }: Props) { {!isLeader && (

{t( - "billing.freePlan.ownerOnly", + "portal.billing.freePlan.ownerOnly", "Only the team owner can switch on the Processor plan.", )}

diff --git a/frontend/portal/src/components/billing/InvoicesList.tsx b/frontend/editor/src/portal/components/billing/InvoicesList.tsx similarity index 82% rename from frontend/portal/src/components/billing/InvoicesList.tsx rename to frontend/editor/src/portal/components/billing/InvoicesList.tsx index 8b13670148..1a9f015f5e 100644 --- a/frontend/portal/src/components/billing/InvoicesList.tsx +++ b/frontend/editor/src/portal/components/billing/InvoicesList.tsx @@ -8,8 +8,8 @@ import { StatusBadge, Table, type TableColumn, -} from "@shared/components"; -import { formatMinor, formatPeriodDate } from "@shared/billing"; +} from "@app/ui"; +import { formatMinor, formatPeriodDate } from "@app/billing"; import { fetchInvoices, type Invoice } from "@portal/api/billing"; const DEFAULT_VISIBLE = 5; @@ -83,13 +83,16 @@ export function InvoicesList() { const columns: TableColumn[] = [ { key: "date", - header: t("billing.invoices.columnDate", "Date"), + header: t("portal.billing.invoices.columnDate", "Date"), render: (inv) => inv.createdAt ? formatPeriodDate(inv.createdAt, { year: true }) : "—", }, { key: "pdfs", - header: t("billing.invoices.columnPdfsProcessed", "PDFs processed"), + header: t( + "portal.billing.invoices.columnPdfsProcessed", + "PDFs processed", + ), align: "right", // Billed units on the invoice's metered line item; "—" when the // line-item table isn't synced into the Stripe mirror. @@ -98,7 +101,7 @@ export function InvoicesList() { }, { key: "amount", - header: t("billing.invoices.columnAmount", "Amount"), + header: t("portal.billing.invoices.columnAmount", "Amount"), align: "right", render: (inv) => inv.totalMinor == null @@ -107,7 +110,7 @@ export function InvoicesList() { }, { key: "status", - header: t("billing.invoices.columnStatus", "Status"), + header: t("portal.billing.invoices.columnStatus", "Status"), render: (inv) => ( {inv.status} @@ -116,11 +119,11 @@ export function InvoicesList() { }, { key: "description", - header: t("billing.invoices.columnDescription", "Description"), + header: t("portal.billing.invoices.columnDescription", "Description"), render: (inv) => ( {inv.description ?? - t("billing.invoices.descriptionFallback", "Invoice")} + t("portal.billing.invoices.descriptionFallback", "Invoice")} ), }, @@ -137,14 +140,14 @@ export function InvoicesList() { target="_blank" rel="noopener noreferrer" aria-label={t( - "billing.invoices.viewAriaLabel", + "portal.billing.invoices.viewAriaLabel", "View invoice {{number}} in Stripe", { number: inv.number ?? inv.id, }, )} > - {t("billing.invoices.viewLink", "View ↗")} + {t("portal.billing.invoices.viewLink", "View ↗")} )} {inv.invoicePdf && ( @@ -154,14 +157,14 @@ export function InvoicesList() { target="_blank" rel="noopener noreferrer" aria-label={t( - "billing.invoices.downloadAriaLabel", + "portal.billing.invoices.downloadAriaLabel", "Download invoice {{number}} as PDF", { number: inv.number ?? inv.id, }, )} > - {t("billing.invoices.pdfLink", "PDF ↓")} + {t("portal.billing.invoices.pdfLink", "PDF ↓")} )} @@ -172,7 +175,7 @@ export function InvoicesList() { return (

- {t("billing.invoices.title", "Invoice history")} + {t("portal.billing.invoices.title", "Invoice history")}

{invoices === null && !error && ( @@ -186,7 +189,7 @@ export function InvoicesList() { {error && (

{t( - "billing.invoices.loadError", + "portal.billing.invoices.loadError", "Couldn't load invoices: {{error}}", { error }, )} @@ -196,9 +199,9 @@ export function InvoicesList() { {invoices !== null && invoices.length === 0 && !error && ( @@ -221,24 +224,28 @@ export function InvoicesList() { > {showAll ? t( - "billing.invoices.showFewer", + "portal.billing.invoices.showFewer", "Show fewer (top {{count}})", { count: DEFAULT_VISIBLE }, ) : atFetchLimit ? t( - "billing.invoices.showMostRecent", + "portal.billing.invoices.showMostRecent", "Show {{count}} most recent", { count: total }, ) - : t("billing.invoices.showAll", "Show all {{count}}", { - count: total, - })} + : t( + "portal.billing.invoices.showAll", + "Show all {{count}}", + { + count: total, + }, + )} {showAll && atFetchLimit && ( {t( - "billing.invoices.fetchLimitNote", + "portal.billing.invoices.fetchLimitNote", "Showing your {{count}} most recent invoices. Older invoices are in the Stripe portal.", { count: FETCH_LIMIT }, )} diff --git a/frontend/portal/src/components/billing/LinkAccountPrompt.stories.tsx b/frontend/editor/src/portal/components/billing/LinkAccountPrompt.stories.tsx similarity index 100% rename from frontend/portal/src/components/billing/LinkAccountPrompt.stories.tsx rename to frontend/editor/src/portal/components/billing/LinkAccountPrompt.stories.tsx diff --git a/frontend/portal/src/components/billing/LinkAccountPrompt.tsx b/frontend/editor/src/portal/components/billing/LinkAccountPrompt.tsx similarity index 76% rename from frontend/portal/src/components/billing/LinkAccountPrompt.tsx rename to frontend/editor/src/portal/components/billing/LinkAccountPrompt.tsx index df7429784e..522fcc17c3 100644 --- a/frontend/portal/src/components/billing/LinkAccountPrompt.tsx +++ b/frontend/editor/src/portal/components/billing/LinkAccountPrompt.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { Button, Card, EmptyState } from "@shared/components"; +import { Button, Card, EmptyState } from "@app/ui"; import { useUI } from "@portal/contexts/UIContext"; /** @@ -14,14 +14,17 @@ export function LinkAccountPrompt() { openLinkModal()}> - {t("billing.linkPrompt.cta", "Link Stirling account")} + {t("portal.billing.linkPrompt.cta", "Link Stirling account")} } /> diff --git a/frontend/portal/src/components/billing/PaymentMethodCard.stories.tsx b/frontend/editor/src/portal/components/billing/PaymentMethodCard.stories.tsx similarity index 100% rename from frontend/portal/src/components/billing/PaymentMethodCard.stories.tsx rename to frontend/editor/src/portal/components/billing/PaymentMethodCard.stories.tsx diff --git a/frontend/portal/src/components/billing/PaymentMethodCard.tsx b/frontend/editor/src/portal/components/billing/PaymentMethodCard.tsx similarity index 79% rename from frontend/portal/src/components/billing/PaymentMethodCard.tsx rename to frontend/editor/src/portal/components/billing/PaymentMethodCard.tsx index ee2a1a4f01..cd3b094a5e 100644 --- a/frontend/portal/src/components/billing/PaymentMethodCard.tsx +++ b/frontend/editor/src/portal/components/billing/PaymentMethodCard.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Button, Card } from "@shared/components"; +import { Button, Card } from "@app/ui"; import { fetchPaymentMethod, type PaymentMethod } from "@portal/api/billing"; interface Props { @@ -46,18 +46,18 @@ export function PaymentMethodCard({ onManage, managing }: Props) {

- {t("billing.paymentMethod.eyebrow", "Payment method")} + {t("portal.billing.paymentMethod.eyebrow", "Payment method")} {hasCard ? ( <>

{t( - "billing.paymentMethod.cardEnding", + "portal.billing.paymentMethod.cardEnding", "{{brand}} ending {{last4}}", { brand: titleCase( pm.brand ?? - t("billing.paymentMethod.cardFallback", "Card"), + t("portal.billing.paymentMethod.cardFallback", "Card"), ), last4: pm.last4, }, @@ -66,23 +66,29 @@ export function PaymentMethodCard({ onManage, managing }: Props) {

{pm.expMonth != null && pm.expYear != null ? t( - "billing.paymentMethod.expiresBilledMonthly", + "portal.billing.paymentMethod.expiresBilledMonthly", "Expires {{expiry}} · billed monthly", { expiry: `${String(pm.expMonth).padStart(2, "0")}/${pm.expYear}`, }, ) - : t("billing.paymentMethod.billedMonthly", "Billed monthly")} + : t( + "portal.billing.paymentMethod.billedMonthly", + "Billed monthly", + )}

) : ( <>

- {t("billing.paymentMethod.managedTitle", "Managed in Stripe")} + {t( + "portal.billing.paymentMethod.managedTitle", + "Managed in Stripe", + )}

{t( - "billing.paymentMethod.managedSub", + "portal.billing.paymentMethod.managedSub", "Your card and billing details are kept securely in Stripe's customer portal.", )}

@@ -95,7 +101,7 @@ export function PaymentMethodCard({ onManage, managing }: Props) { loading={managing} onClick={onManage} > - {t("billing.paymentMethod.update", "Update")} + {t("portal.billing.paymentMethod.update", "Update")}
diff --git a/frontend/portal/src/components/billing/PdfsProcessedCard.stories.tsx b/frontend/editor/src/portal/components/billing/PdfsProcessedCard.stories.tsx similarity index 100% rename from frontend/portal/src/components/billing/PdfsProcessedCard.stories.tsx rename to frontend/editor/src/portal/components/billing/PdfsProcessedCard.stories.tsx diff --git a/frontend/portal/src/components/billing/PdfsProcessedCard.tsx b/frontend/editor/src/portal/components/billing/PdfsProcessedCard.tsx similarity index 80% rename from frontend/portal/src/components/billing/PdfsProcessedCard.tsx rename to frontend/editor/src/portal/components/billing/PdfsProcessedCard.tsx index ba5ac17ea9..aec101ea83 100644 --- a/frontend/portal/src/components/billing/PdfsProcessedCard.tsx +++ b/frontend/editor/src/portal/components/billing/PdfsProcessedCard.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { Card } from "@shared/components"; +import { Card } from "@app/ui"; import type { Wallet, WalletCategoryBreakdown } from "@portal/api/billing"; /** @@ -19,25 +19,25 @@ const SEGMENTS: ReadonlyArray<{ }> = [ { key: "api", - labelKey: "billing.pdfsProcessed.segmentApiLabel", + labelKey: "portal.billing.pdfsProcessed.segmentApiLabel", labelDefault: "API", - descKey: "billing.pdfsProcessed.segmentApiDesc", + descKey: "portal.billing.pdfsProcessed.segmentApiDesc", descDefault: "Direct API requests", cls: "blue", }, { key: "ai", - labelKey: "billing.pdfsProcessed.segmentAgentsLabel", + labelKey: "portal.billing.pdfsProcessed.segmentAgentsLabel", labelDefault: "Agents", - descKey: "billing.pdfsProcessed.segmentAgentsDesc", + descKey: "portal.billing.pdfsProcessed.segmentAgentsDesc", descDefault: "AI agent actions", cls: "purple", }, { key: "automation", - labelKey: "billing.pdfsProcessed.segmentAutomationLabel", + labelKey: "portal.billing.pdfsProcessed.segmentAutomationLabel", labelDefault: "Automation", - descKey: "billing.pdfsProcessed.segmentAutomationDesc", + descKey: "portal.billing.pdfsProcessed.segmentAutomationDesc", descDefault: "Automations & pipelines", cls: "teal", }, @@ -51,14 +51,17 @@ export function PdfsProcessedCard({ wallet }: { wallet: Wallet }) { return ( - {t("billing.pdfsProcessed.eyebrow", "PDFs processed this period")} + {t( + "portal.billing.pdfsProcessed.eyebrow", + "PDFs processed this period", + )}
{wallet.billableUsed.toLocaleString()} - {t("billing.pdfsProcessed.unit", "metered PDFs")} + {t("portal.billing.pdfsProcessed.unit", "metered PDFs")}
@@ -68,7 +71,7 @@ export function PdfsProcessedCard({ wallet }: { wallet: Wallet }) { className="portal-billing__segbar" role="img" aria-label={t( - "billing.pdfsProcessed.segbarAriaLabel", + "portal.billing.pdfsProcessed.segbarAriaLabel", "Metered PDFs split by category", )} > @@ -94,7 +97,7 @@ export function PdfsProcessedCard({ wallet }: { wallet: Wallet }) { {t( - "billing.pdfsProcessed.legendValue", + "portal.billing.pdfsProcessed.legendValue", "{{formatted}} PDFs", { count: b[s.key], @@ -112,7 +115,7 @@ export function PdfsProcessedCard({ wallet }: { wallet: Wallet }) { ) : (

{t( - "billing.pdfsProcessed.emptyPeriod", + "portal.billing.pdfsProcessed.emptyPeriod", "No metered processing yet this period.", )}

diff --git a/frontend/portal/src/components/billing/SpendLimitCard.stories.tsx b/frontend/editor/src/portal/components/billing/SpendLimitCard.stories.tsx similarity index 100% rename from frontend/portal/src/components/billing/SpendLimitCard.stories.tsx rename to frontend/editor/src/portal/components/billing/SpendLimitCard.stories.tsx diff --git a/frontend/portal/src/components/billing/SpendLimitCard.tsx b/frontend/editor/src/portal/components/billing/SpendLimitCard.tsx similarity index 84% rename from frontend/portal/src/components/billing/SpendLimitCard.tsx rename to frontend/editor/src/portal/components/billing/SpendLimitCard.tsx index 2593810017..d117f3d9bc 100644 --- a/frontend/portal/src/components/billing/SpendLimitCard.tsx +++ b/frontend/editor/src/portal/components/billing/SpendLimitCard.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Banner, Button, Card } from "@shared/components"; +import { Banner, Button, Card } from "@app/ui"; import { currencySymbol, docCapForMoney, @@ -9,7 +9,7 @@ import { MeterBar, meterState, SpendCapControl as SharedSpendCapControl, -} from "@shared/billing"; +} from "@app/billing"; import type { Wallet } from "@portal/api/billing"; import { updateCap } from "@portal/api/billing"; @@ -127,10 +127,10 @@ export function SpendLimitCard({ return ( - {t("billing.spendLimit.eyebrow", "Spend limit")} + {t("portal.billing.spendLimit.eyebrow", "Spend limit")}

- {t("billing.spendLimit.editTitle", "Set your monthly ceiling")} + {t("portal.billing.spendLimit.editTitle", "Set your monthly ceiling")}

@@ -151,7 +151,7 @@ export function SpendLimitCard({ onClick={() => setDraftCap(proj.suggestedMajor)} > {t( - "billing.spendLimit.useSuggested", + "portal.billing.spendLimit.useSuggested", "Use suggested · {{amount}} / month", { amount: formatMoneyMajor(proj.suggestedMajor, wallet.currency), @@ -162,10 +162,10 @@ export function SpendLimitCard({
- {t("billing.spendLimit.guardrailLabel", "Your guardrail:")} + {t("portal.billing.spendLimit.guardrailLabel", "Your guardrail:")} {" "} {t( - "billing.spendLimit.guardrailBody", + "portal.billing.spendLimit.guardrailBody", "a hard ceiling — you're never billed past it. At the cap, metered processing pauses (unlimited PDF editing keeps working) until you raise it or the cycle resets. Nothing is lost.", )}
@@ -173,7 +173,10 @@ export function SpendLimitCard({ {error && ( {error} @@ -185,10 +188,10 @@ export function SpendLimitCard({ size="sm" onClick={() => onAdjustingChange(false)} > - {t("billing.spendLimit.cancel", "Cancel")} + {t("portal.billing.spendLimit.cancel", "Cancel")}
@@ -209,11 +212,11 @@ export function SpendLimitCard({
- {t("billing.spendLimit.eyebrow", "Spend limit")} + {t("portal.billing.spendLimit.eyebrow", "Spend limit")}

{t( - "billing.spendLimit.displaySub", + "portal.billing.spendLimit.displaySub", "You're only billed for what you process automatically — never past the ceiling.", )}

@@ -224,7 +227,7 @@ export function SpendLimitCard({ size="sm" onClick={() => onAdjustingChange(true)} > - {t("billing.spendLimit.adjustLimit", "Adjust limit")} + {t("portal.billing.spendLimit.adjustLimit", "Adjust limit")} )}
@@ -240,18 +243,18 @@ export function SpendLimitCard({ capActive ? docEstimate != null ? t( - "billing.spendLimit.capSuffixWithDocs", + "portal.billing.spendLimit.capSuffixWithDocs", "/ month · ≈ {{documents}} documents", { documents: docEstimate.toLocaleString(), }, ) - : t("billing.spendLimit.capSuffix", "/ month") - : t("billing.spendLimit.noCap", "no cap") + : t("portal.billing.spendLimit.capSuffix", "/ month") + : t("portal.billing.spendLimit.noCap", "no cap") } statusLabel={ capActive - ? t("billing.spendLimit.pctUsed", "{{pct}}% used", { + ? t("portal.billing.spendLimit.pctUsed", "{{pct}}% used", { pct: Math.round(pct), }) : null @@ -262,7 +265,7 @@ export function SpendLimitCard({ <> {t( - "billing.spendLimit.usedThisMonth", + "portal.billing.spendLimit.usedThisMonth", "{{amount}} used this month", { amount: spentLabel, @@ -270,15 +273,19 @@ export function SpendLimitCard({ )} - {t("billing.spendLimit.remaining", "{{amount}} remaining", { - amount: formatMinor(remainingMinor, wallet.currency), - })} + {t( + "portal.billing.spendLimit.remaining", + "{{amount}} remaining", + { + amount: formatMinor(remainingMinor, wallet.currency), + }, + )} ) : ( {t( - "billing.spendLimit.thisPeriodUncapped", + "portal.billing.spendLimit.thisPeriodUncapped", "{{amount}} this period · uncapped", { amount: spentLabel, @@ -293,10 +300,13 @@ export function SpendLimitCard({ {proj && (

- {t("billing.spendLimit.projection.label", "Projected to exceed.")} + {t( + "portal.billing.spendLimit.projection.label", + "Projected to exceed.", + )} {" "} {t( - "billing.spendLimit.projection.body", + "portal.billing.spendLimit.projection.body", "At {{rate}}/day you reach the cap in ~{{count}} days (~{{monthEnd}} month-end). Suggested limit ~{{suggested}}.", { count: proj.daysToCap, diff --git a/frontend/portal/src/components/billing/SpendThisMonthCard.stories.tsx b/frontend/editor/src/portal/components/billing/SpendThisMonthCard.stories.tsx similarity index 100% rename from frontend/portal/src/components/billing/SpendThisMonthCard.stories.tsx rename to frontend/editor/src/portal/components/billing/SpendThisMonthCard.stories.tsx diff --git a/frontend/portal/src/components/billing/SpendThisMonthCard.tsx b/frontend/editor/src/portal/components/billing/SpendThisMonthCard.tsx similarity index 86% rename from frontend/portal/src/components/billing/SpendThisMonthCard.tsx rename to frontend/editor/src/portal/components/billing/SpendThisMonthCard.tsx index 2a8baf2986..d4a969ae0c 100644 --- a/frontend/portal/src/components/billing/SpendThisMonthCard.tsx +++ b/frontend/editor/src/portal/components/billing/SpendThisMonthCard.tsx @@ -1,6 +1,6 @@ import { useTranslation } from "react-i18next"; -import { Card } from "@shared/components"; -import { formatMinor } from "@shared/billing"; +import { Card } from "@app/ui"; +import { formatMinor } from "@app/billing"; import type { Wallet } from "@portal/api/billing"; import { EnterpriseUpsell } from "@portal/components/billing/EnterpriseUpsell"; @@ -19,7 +19,7 @@ export function SpendThisMonthCard({ wallet }: { wallet: Wallet }) { return ( - {t("billing.spendThisMonth.eyebrow", "Spend this month")} + {t("portal.billing.spendThisMonth.eyebrow", "Spend this month")}

@@ -29,7 +29,7 @@ export function SpendThisMonthCard({ wallet }: { wallet: Wallet }) {

{rateLabel ? t( - "billing.spendThisMonth.processedWithRate", + "portal.billing.spendThisMonth.processedWithRate", "{{formattedCount}} PDFs processed, at {{rate}} each.", { count: wallet.billableUsed, @@ -38,7 +38,7 @@ export function SpendThisMonthCard({ wallet }: { wallet: Wallet }) { }, ) : t( - "billing.spendThisMonth.processed", + "portal.billing.spendThisMonth.processed", "{{formattedCount}} PDFs processed.", { count: wallet.billableUsed, diff --git a/frontend/portal/src/components/billing/StripeCheckoutModal.tsx b/frontend/editor/src/portal/components/billing/StripeCheckoutModal.tsx similarity index 89% rename from frontend/portal/src/components/billing/StripeCheckoutModal.tsx rename to frontend/editor/src/portal/components/billing/StripeCheckoutModal.tsx index 24d5cbb251..313694baa9 100644 --- a/frontend/portal/src/components/billing/StripeCheckoutModal.tsx +++ b/frontend/editor/src/portal/components/billing/StripeCheckoutModal.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Banner, Modal, Skeleton } from "@shared/components"; +import { Banner, Modal, Skeleton } from "@app/ui"; import { EmbeddedCheckout, EmbeddedCheckoutProvider, @@ -91,7 +91,7 @@ export function StripeCheckoutModal({ if (!session.clientSecret) { setError( t( - "billing.checkout.noClientSecret", + "portal.billing.checkout.noClientSecret", "Edge function returned no client_secret.", ), ); @@ -120,9 +120,9 @@ export function StripeCheckoutModal({ open={open} onClose={onClose} width="lg" - title={t("billing.checkout.title", "Turn on the Processor plan")} + title={t("portal.billing.checkout.title", "Turn on the Processor plan")} subtitle={t( - "billing.checkout.subtitle", + "portal.billing.checkout.subtitle", "Add a card to keep going past your free Editor-plan grant. Stripe handles the rest.", )} > @@ -130,14 +130,14 @@ export function StripeCheckoutModal({ - {t("billing.checkout.notConfigured.bodyBefore", "Set")}{" "} + {t("portal.billing.checkout.notConfigured.bodyBefore", "Set")}{" "} VITE_STRIPE_PUBLISHABLE_KEY{" "} {t( - "billing.checkout.notConfigured.bodyAfter", + "portal.billing.checkout.notConfigured.bodyAfter", "in the portal env to enable in-app checkout.", )} @@ -145,7 +145,10 @@ export function StripeCheckoutModal({ {publishableKey && error && ( {error} diff --git a/frontend/portal/src/components/billing/SubscribedPlanView.stories.tsx b/frontend/editor/src/portal/components/billing/SubscribedPlanView.stories.tsx similarity index 100% rename from frontend/portal/src/components/billing/SubscribedPlanView.stories.tsx rename to frontend/editor/src/portal/components/billing/SubscribedPlanView.stories.tsx diff --git a/frontend/portal/src/components/billing/SubscribedPlanView.tsx b/frontend/editor/src/portal/components/billing/SubscribedPlanView.tsx similarity index 87% rename from frontend/portal/src/components/billing/SubscribedPlanView.tsx rename to frontend/editor/src/portal/components/billing/SubscribedPlanView.tsx index 6ccdbe7e8d..37bf35a54a 100644 --- a/frontend/portal/src/components/billing/SubscribedPlanView.tsx +++ b/frontend/editor/src/portal/components/billing/SubscribedPlanView.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { Banner, Button } from "@shared/components"; -import { meterState } from "@shared/billing"; +import { Banner, Button } from "@app/ui"; +import { meterState } from "@app/billing"; import type { Wallet } from "@portal/api/billing"; import { useStripePortal } from "@portal/hooks/useStripePortal"; import { FreePdfEditorsCard } from "@portal/components/billing/FreePdfEditorsCard"; @@ -57,11 +57,11 @@ export function SubscribedPlanView({ wallet, onWalletChange }: Props) { title={ state === "DEGRADED" ? t( - "billing.subscribedPlan.capWarn.reachedTitle", + "portal.billing.subscribedPlan.capWarn.reachedTitle", "Monthly spend limit reached", ) : t( - "billing.subscribedPlan.capWarn.approachingTitle", + "portal.billing.subscribedPlan.capWarn.approachingTitle", "You're at {{pct}}% of your monthly spend limit", { pct: Math.round(pct), @@ -71,18 +71,21 @@ export function SubscribedPlanView({ wallet, onWalletChange }: Props) { action={ isLeader ? ( ) : undefined } > {state === "DEGRADED" ? t( - "billing.subscribedPlan.capWarn.reachedBody", + "portal.billing.subscribedPlan.capWarn.reachedBody", "Metered processing is paused until you raise the limit or the cycle resets. Unlimited PDF editing keeps working.", ) : t( - "billing.subscribedPlan.capWarn.approachingBody", + "portal.billing.subscribedPlan.capWarn.approachingBody", "Raise it now so automated processing never pauses.", )} @@ -110,7 +113,7 @@ export function SubscribedPlanView({ wallet, onWalletChange }: Props) { diff --git a/frontend/portal/src/components/billing/WalletMeter.stories.tsx b/frontend/editor/src/portal/components/billing/WalletMeter.stories.tsx similarity index 100% rename from frontend/portal/src/components/billing/WalletMeter.stories.tsx rename to frontend/editor/src/portal/components/billing/WalletMeter.stories.tsx diff --git a/frontend/portal/src/components/billing/WalletMeter.tsx b/frontend/editor/src/portal/components/billing/WalletMeter.tsx similarity index 78% rename from frontend/portal/src/components/billing/WalletMeter.tsx rename to frontend/editor/src/portal/components/billing/WalletMeter.tsx index c8a2ed81de..1cb7f5794f 100644 --- a/frontend/portal/src/components/billing/WalletMeter.tsx +++ b/frontend/editor/src/portal/components/billing/WalletMeter.tsx @@ -1,7 +1,7 @@ import type { ReactNode } from "react"; import { useTranslation } from "react-i18next"; -import { Card } from "@shared/components"; -import { formatMinor, MeterBar, meterState } from "@shared/billing"; +import { Card } from "@app/ui"; +import { formatMinor, MeterBar, meterState } from "@app/billing"; import type { Wallet } from "@portal/api/billing"; interface Props { @@ -27,7 +27,7 @@ export function WalletMeter({ wallet, action }: Props) { const title = rate != null ? t( - "billing.walletMeter.titleWithRate", + "portal.billing.walletMeter.titleWithRate", "Process {{allowance}} PDFs free, then {{rate}}/PDF", { count: wallet.freeAllowance, @@ -35,22 +35,26 @@ export function WalletMeter({ wallet, action }: Props) { rate: formatMinor(rate, wallet.currency), }, ) - : t("billing.walletMeter.title", "Process {{allowance}} PDFs free", { - count: wallet.freeAllowance, - allowance: wallet.freeAllowance.toLocaleString(), - }); + : t( + "portal.billing.walletMeter.title", + "Process {{allowance}} PDFs free", + { + count: wallet.freeAllowance, + allowance: wallet.freeAllowance.toLocaleString(), + }, + ); return (

- {t("billing.walletMeter.eyebrow", "Processor trial")} + {t("portal.billing.walletMeter.eyebrow", "Processor trial")}

{title}

{t( - "billing.walletMeter.sub", + "portal.billing.walletMeter.sub", "Use the PDF Editor for free. Pay to process PDFs automatically.", )}

@@ -63,7 +67,7 @@ export function WalletMeter({ wallet, action }: Props) { pct={pct} figure={wallet.billableUsed.toLocaleString()} capSuffix={t( - "billing.walletMeter.capSuffix", + "portal.billing.walletMeter.capSuffix", "of {{allowance}} free PDFs used", { count: wallet.freeAllowance, @@ -71,7 +75,7 @@ export function WalletMeter({ wallet, action }: Props) { }, )} statusLabel={t( - "billing.walletMeter.statusLabel", + "portal.billing.walletMeter.statusLabel", "{{remaining}} left", { count: wallet.freeRemaining, diff --git a/frontend/portal/src/components/billing/billing.css b/frontend/editor/src/portal/components/billing/billing.css similarity index 100% rename from frontend/portal/src/components/billing/billing.css rename to frontend/editor/src/portal/components/billing/billing.css diff --git a/frontend/portal/src/components/billing/walletFixtures.ts b/frontend/editor/src/portal/components/billing/walletFixtures.ts similarity index 100% rename from frontend/portal/src/components/billing/walletFixtures.ts rename to frontend/editor/src/portal/components/billing/walletFixtures.ts diff --git a/frontend/portal/src/components/catalogue/ComponentCard.stories.tsx b/frontend/editor/src/portal/components/catalogue/ComponentCard.stories.tsx similarity index 100% rename from frontend/portal/src/components/catalogue/ComponentCard.stories.tsx rename to frontend/editor/src/portal/components/catalogue/ComponentCard.stories.tsx diff --git a/frontend/portal/src/components/catalogue/ComponentCard.tsx b/frontend/editor/src/portal/components/catalogue/ComponentCard.tsx similarity index 90% rename from frontend/portal/src/components/catalogue/ComponentCard.tsx rename to frontend/editor/src/portal/components/catalogue/ComponentCard.tsx index 9c662d5b9b..07d3af75c1 100644 --- a/frontend/portal/src/components/catalogue/ComponentCard.tsx +++ b/frontend/editor/src/portal/components/catalogue/ComponentCard.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { Card, Chip, StatusBadge } from "@shared/components"; +import { Card, Chip, StatusBadge } from "@app/ui"; import { type SdkComponent, MATURITY_META, @@ -30,7 +30,9 @@ export function ComponentCard({ className={"portal-components__card" + (unlocked ? "" : " is-locked")} role="button" tabIndex={0} - aria-label={t("catalogue.card.openAriaLabel", { name: component.name })} + aria-label={t("portal.catalogue.card.openAriaLabel", { + name: component.name, + })} onClick={() => onOpen(component)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { @@ -47,7 +49,7 @@ export function ComponentCard({ {!unlocked && ( 🔒 diff --git a/frontend/portal/src/components/catalogue/ComponentDetailModal.stories.tsx b/frontend/editor/src/portal/components/catalogue/ComponentDetailModal.stories.tsx similarity index 100% rename from frontend/portal/src/components/catalogue/ComponentDetailModal.stories.tsx rename to frontend/editor/src/portal/components/catalogue/ComponentDetailModal.stories.tsx diff --git a/frontend/portal/src/components/catalogue/ComponentDetailModal.tsx b/frontend/editor/src/portal/components/catalogue/ComponentDetailModal.tsx similarity index 79% rename from frontend/portal/src/components/catalogue/ComponentDetailModal.tsx rename to frontend/editor/src/portal/components/catalogue/ComponentDetailModal.tsx index 970d2fe392..1959e10b1d 100644 --- a/frontend/portal/src/components/catalogue/ComponentDetailModal.tsx +++ b/frontend/editor/src/portal/components/catalogue/ComponentDetailModal.tsx @@ -9,7 +9,7 @@ import { StatTile, StatusBadge, Tabs, -} from "@shared/components"; +} from "@app/ui"; import { type SdkComponent, MATURITY_META, @@ -49,14 +49,14 @@ export function ComponentDetailModal({ ); } const tabs = TAB_KEYS.map((key) => ({ key, - label: t(`catalogue.detail.tabs.${key}`), + label: t(`portal.catalogue.detail.tabs.${key}`), })); const maturity = MATURITY_META[component.maturity]; @@ -92,7 +92,7 @@ export function ComponentDetailModal({ // publishable key scoped to this component. onClick={() => onClose()} > - {t("catalogue.detail.addToProject")} + {t("portal.catalogue.detail.addToProject")}
) : ( @@ -102,7 +102,7 @@ export function ComponentDetailModal({ // TODO(backend): route to the upgrade / contact-sales flow. onClick={() => onClose()} > - {t("catalogue.detail.upgradeToUnlock")} + {t("portal.catalogue.detail.upgradeToUnlock")} ) } @@ -110,8 +110,8 @@ export function ComponentDetailModal({ {!unlocked && ( here, booting the component against a demo document and the dev's publishable key. */} - {t("catalogue.detail.preview.badge")} + {t("portal.catalogue.detail.preview.badge")} - {t("catalogue.detail.preview.note")} + {t("portal.catalogue.detail.preview.note")}
@@ -136,7 +136,7 @@ export function ComponentDetailModal({ activeKey={tab} onChange={setTab} variant="underline" - ariaLabel={t("catalogue.detail.tabsAriaLabel")} + ariaLabel={t("portal.catalogue.detail.tabsAriaLabel")} />
@@ -154,25 +154,25 @@ export function ComponentDetailModal({
0 - ? t("catalogue.detail.stats.freeQuotaValue", { + ? t("portal.catalogue.detail.stats.freeQuotaValue", { amount: component.pricing.freeQuota.toLocaleString(), }) - : t("catalogue.detail.stats.none") + : t("portal.catalogue.detail.stats.none") } />
@@ -184,12 +184,12 @@ export function ComponentDetailModal({
)} @@ -200,26 +200,26 @@ export function ComponentDetailModal({
0 - ? t("catalogue.detail.stats.freeQuotaValue", { + ? t("portal.catalogue.detail.stats.freeQuotaValue", { amount: component.pricing.freeQuota.toLocaleString(), }) - : t("catalogue.detail.stats.none") + : t("portal.catalogue.detail.stats.none") } />

- {t("catalogue.detail.pricing.note", { + {t("portal.catalogue.detail.pricing.note", { unit: component.pricing.unit, })}

diff --git a/frontend/portal/src/components/catalogue/ComponentGrid.stories.tsx b/frontend/editor/src/portal/components/catalogue/ComponentGrid.stories.tsx similarity index 100% rename from frontend/portal/src/components/catalogue/ComponentGrid.stories.tsx rename to frontend/editor/src/portal/components/catalogue/ComponentGrid.stories.tsx diff --git a/frontend/portal/src/components/catalogue/ComponentGrid.tsx b/frontend/editor/src/portal/components/catalogue/ComponentGrid.tsx similarity index 100% rename from frontend/portal/src/components/catalogue/ComponentGrid.tsx rename to frontend/editor/src/portal/components/catalogue/ComponentGrid.tsx diff --git a/frontend/portal/src/components/catalogue/ComponentPropsTable.stories.tsx b/frontend/editor/src/portal/components/catalogue/ComponentPropsTable.stories.tsx similarity index 100% rename from frontend/portal/src/components/catalogue/ComponentPropsTable.stories.tsx rename to frontend/editor/src/portal/components/catalogue/ComponentPropsTable.stories.tsx diff --git a/frontend/portal/src/components/catalogue/ComponentPropsTable.tsx b/frontend/editor/src/portal/components/catalogue/ComponentPropsTable.tsx similarity index 77% rename from frontend/portal/src/components/catalogue/ComponentPropsTable.tsx rename to frontend/editor/src/portal/components/catalogue/ComponentPropsTable.tsx index 0bb77cb47a..8ff5155b63 100644 --- a/frontend/portal/src/components/catalogue/ComponentPropsTable.tsx +++ b/frontend/editor/src/portal/components/catalogue/ComponentPropsTable.tsx @@ -1,6 +1,6 @@ import { useMemo } from "react"; import { useTranslation } from "react-i18next"; -import { Chip, Table, type TableColumn } from "@shared/components"; +import { Chip, Table, type TableColumn } from "@app/ui"; import type { ComponentProp } from "@portal/api/sdkComponents"; import "@portal/views/Components.css"; @@ -15,35 +15,35 @@ export function ComponentPropsTable({ props: rows }: ComponentPropsTableProps) { () => [ { key: "name", - header: t("catalogue.props.columns.name"), + header: t("portal.catalogue.props.columns.name"), render: (p) => ( {p.name} ), }, { key: "type", - header: t("catalogue.props.columns.type"), + header: t("portal.catalogue.props.columns.type"), render: (p) => ( {p.type} ), }, { key: "required", - header: t("catalogue.props.columns.required"), + header: t("portal.catalogue.props.columns.required"), render: (p) => p.required ? ( - {t("catalogue.props.required")} + {t("portal.catalogue.props.required")} ) : ( - {t("catalogue.props.optional")} + {t("portal.catalogue.props.optional")} ), }, { key: "description", - header: t("catalogue.props.columns.description"), + header: t("portal.catalogue.props.columns.description"), render: (p) => ( {p.description} ), diff --git a/frontend/portal/src/components/catalogue/ComponentsSummaryStrip.tsx b/frontend/editor/src/portal/components/catalogue/ComponentsSummaryStrip.tsx similarity index 82% rename from frontend/portal/src/components/catalogue/ComponentsSummaryStrip.tsx rename to frontend/editor/src/portal/components/catalogue/ComponentsSummaryStrip.tsx index ec46ad855f..ccb3965a5c 100644 --- a/frontend/portal/src/components/catalogue/ComponentsSummaryStrip.tsx +++ b/frontend/editor/src/portal/components/catalogue/ComponentsSummaryStrip.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { MetricCard, MetricStrip } from "@shared/components"; +import { MetricCard, MetricStrip } from "@app/ui"; import type { ComponentsResponse } from "@portal/api/sdkComponents"; /** @@ -8,10 +8,10 @@ import type { ComponentsResponse } from "@portal/api/sdkComponents"; * Only values flow from the API. */ const KPI_LABEL_KEYS = [ - "catalogue.summary.componentsGa", - "catalogue.summary.inBeta", - "catalogue.summary.embedsThisMonth", - "catalogue.summary.componentSpendMtd", + "portal.catalogue.summary.componentsGa", + "portal.catalogue.summary.inBeta", + "portal.catalogue.summary.embedsThisMonth", + "portal.catalogue.summary.componentSpendMtd", ] as const; interface ComponentsSummaryStripProps { diff --git a/frontend/portal/src/components/docs/AuthenticationSection.stories.tsx b/frontend/editor/src/portal/components/docs/AuthenticationSection.stories.tsx similarity index 100% rename from frontend/portal/src/components/docs/AuthenticationSection.stories.tsx rename to frontend/editor/src/portal/components/docs/AuthenticationSection.stories.tsx diff --git a/frontend/portal/src/components/docs/AuthenticationSection.tsx b/frontend/editor/src/portal/components/docs/AuthenticationSection.tsx similarity index 65% rename from frontend/portal/src/components/docs/AuthenticationSection.tsx rename to frontend/editor/src/portal/components/docs/AuthenticationSection.tsx index 65b1d89599..e15e04592c 100644 --- a/frontend/portal/src/components/docs/AuthenticationSection.tsx +++ b/frontend/editor/src/portal/components/docs/AuthenticationSection.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { Chip, CodeBlock } from "@shared/components"; +import { Chip, CodeBlock } from "@app/ui"; import { DocsSection } from "@portal/components/docs/DocsSection"; export function AuthenticationSection() { @@ -7,13 +7,13 @@ export function AuthenticationSection() { return (
@@ -21,13 +21,13 @@ export function AuthenticationSection() { sk_live_ - {t("docs.authentication.liveKey")} + {t("portal.docs.authentication.liveKey")}
sk_test_ - {t("docs.authentication.testKey")} + {t("portal.docs.authentication.testKey")}
diff --git a/frontend/portal/src/components/docs/ComponentsSection.stories.tsx b/frontend/editor/src/portal/components/docs/ComponentsSection.stories.tsx similarity index 100% rename from frontend/portal/src/components/docs/ComponentsSection.stories.tsx rename to frontend/editor/src/portal/components/docs/ComponentsSection.stories.tsx diff --git a/frontend/portal/src/components/docs/ComponentsSection.tsx b/frontend/editor/src/portal/components/docs/ComponentsSection.tsx similarity index 81% rename from frontend/portal/src/components/docs/ComponentsSection.tsx rename to frontend/editor/src/portal/components/docs/ComponentsSection.tsx index 10a2349154..5ad8c4b502 100644 --- a/frontend/portal/src/components/docs/ComponentsSection.tsx +++ b/frontend/editor/src/portal/components/docs/ComponentsSection.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { Card, Chip, CodeBlock } from "@shared/components"; +import { Card, Chip, CodeBlock } from "@app/ui"; import type { EmbedComponent } from "@portal/api/docs"; import { DocsSection } from "@portal/components/docs/DocsSection"; @@ -12,9 +12,9 @@ export function ComponentsSection({ return (
{components.map((c) => ( @@ -31,7 +31,7 @@ export function ComponentsSection({
+