Skip to content

Commit 919f0ad

Browse files
reecebrowneclaude
andauthored
# Description of Changes ## What & why This PR introduces the **Stirling developer portal** — a new control-plane frontend that sits alongside the existing PDF editor — plus the shared design system and workspace structure needed to host both apps in one frontend. The portal is the parent product surface: where users connect sources, compose pipelines, wire agents, and manage usage / billing / infrastructure, with the PDF editor as one capability inside it. This PR lays the **foundation** — workspace reshape, design system, app shell, navigation, and a mock-driven home — rather than wiring real backends (those surfaces are placeholders for follow-up phases). ## What's in this PR **1. Frontend repo reshape (`frontend/src/` → `frontend/editor/`)** The existing editor app moved under `frontend/editor/`, so `editor`, `portal`, and `shared` are siblings in one workspace. All references were updated accordingly: `LICENSE`, `.dockerignore`, `.gitignore`, build/sign shell scripts, the GH language-check script, the Taskfile, and Docker config. **No editor source logic changed — path references only.** **2. New shared design system (`frontend/shared/`)** - **Design tokens** in `tokens.css` as the single runtime source of truth (light/dark, category accents, gradients). `tokens.ts` now holds only the `Tier` type — the old JS palette mirror was removed (nothing consumed it and it had drifted). - ~30 framework-light **components** (Card, Button, Input, Select, Tabs, Modal, Drawer, Toast, MetricCard, StatusBadge, Skeleton, EmptyState, …) with Storybook stories. - **Typed data catalogues**: `endpoints.ts` (10 verticals / 64 endpoints) and `ops.ts`. **3. New developer portal app (`frontend/portal/`)** - App shell: `Header`, `Sidebar`, `AssistantPanel`, search modal, notifications, tier switcher, theme toggle, MSW toggle. - **Tier-aware** home (free / pay-as-you-go / enterprise): KPI strip, 30-day usage chart, onboarding checklist, quick actions, recent activity, region health, product grid, and a curated **"Popular use cases"** teaser. - **Documents** view hosting the full, tab-filterable endpoint catalogue. - Placeholder views for Sources / Pipelines / Agents / Editor / Infrastructure / Usage & Billing / Developer Docs / Settings (follow-up phases). - **MSW-mocked** API layer: `api/*` issues real `fetch`, intercepted by mocks in dev/Storybook; pointing at a real backend is just a matter of not registering MSW. `react-router` URLs; Tier / View / UI contexts. **4. Tooling & guardrails** - ESLint extended to `portal` + `shared`, with **layering-boundary rules**: `shared/` may depend only on third-party packages and itself (no `@app` / `@portal` / `@core` / `@proprietary` / Tauri), so it stays cleanly extractable into a standalone package later. - `dpdm` circular-dependency check now walks editor + portal + shared (the old glob matched only 2 files). - New **devDependencies only** — Storybook (+ a11y/docs/themes addons), MSW. No runtime dependencies added. - New tasks: `frontend:dev:portal`, `frontend:build:portal`. ## Testing done locally - `tsc` for both `portal` and `shared` projects — clean - `eslint --max-warnings=0` across the whole frontend — clean - `dpdm` circular-dependency check — no cycles - Editor builds clean: `vite build editor --mode core` (✓ built, only the pre-existing >500 kB chunk-size advisory) - Editor runs in dev (core mode) with **zero console errors**; portal runs in dev across all three tiers ## Notes for reviewers - The change is overwhelmingly **additive**: `shared/` and `portal/` are brand-new; the existing editor is path-reference changes only. - The portal is intentionally **mock-driven** at this stage — real backends and the remaining views land in follow-up phases. --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have run `task check` to verify linters, typechecks, and tests pass - [x] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing) for more details. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent b355cce commit 919f0ad

201 files changed

Lines changed: 19955 additions & 86 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.dockerignore

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ version_builds/
2626
node_modules/
2727
**/node_modules/
2828
frontend/node_modules/
29-
frontend/dist/
30-
frontend/playwright-report/
29+
frontend/editor/dist/
30+
frontend/dist-portal/
31+
frontend/editor/playwright-report/
3132
.npm/
3233
.yarn/
3334

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ out/
212212
*.asc
213213

214214
# Allow test fixture certificates (synthetic, no real credentials)
215-
!frontend/src/core/tests/test-fixtures/certs/**
215+
!frontend/editor/src/core/tests/test-fixtures/certs/**
216216

217217
# SSH Keys
218218
*.pub
@@ -254,7 +254,7 @@ node_modules/
254254
*compact*.json
255255
test_batch.json
256256
*.backup.*.json
257-
frontend/public/locales/*/translation.backup*.json
257+
frontend/editor/public/locales/*/translation.backup*.json
258258

259259
# Development/build artifacts
260260
.gradle-cache/

.taskfiles/frontend.yml

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,12 @@ tasks:
112112
- task: dev:_run
113113
vars: { MODE: prototypes, PORT: '{{.PORT}}', BACKEND_URL: '{{.BACKEND_URL}}', OPEN: '{{.OPEN}}' }
114114

115+
dev:portal:
116+
desc: "Start developer portal dev server"
117+
deps: [install]
118+
cmds:
119+
- npx vite portal --port {{.PORT | default "5173"}}{{if .OPEN}} --open{{end}}
120+
115121
# ============================================================
116122
# Build
117123
# ============================================================
@@ -156,6 +162,24 @@ tasks:
156162
cmds:
157163
- npx vite build editor --mode prototypes
158164

165+
build:portal:
166+
desc: "Build developer portal"
167+
deps: [install]
168+
cmds:
169+
- npx vite build portal
170+
171+
storybook:
172+
desc: "Start Storybook dev server"
173+
deps: [install]
174+
cmds:
175+
- npx storybook dev -p 6006 {{.CLI_ARGS}}
176+
177+
storybook:build:
178+
desc: "Build static Storybook"
179+
deps: [install]
180+
cmds:
181+
- npx storybook build {{.CLI_ARGS}}
182+
159183
# ============================================================
160184
# Code quality
161185
# ============================================================
@@ -165,7 +189,10 @@ tasks:
165189
deps: [install]
166190
cmds:
167191
- npx eslint --max-warnings=0
168-
- npx dpdm editor/src --circular --no-warning --no-tree --exit-code circular:1
192+
# Globs (not a bare dir) so dpdm walks the whole tree — `editor/src`
193+
# alone matched only 2 files. dpdm expands the braces itself, so this is
194+
# shell-agnostic. Covers editor, portal, and the shared design system.
195+
- 'npx dpdm "editor/src/**/*.{ts,tsx}" "portal/src/**/*.{ts,tsx}" "shared/**/*.{ts,tsx}" --circular --no-warning --no-tree --exit-code circular:1'
169196

170197
lint:fix:
171198
desc: "Auto-fix lint issues"
@@ -236,6 +263,18 @@ tasks:
236263
cmds:
237264
- npx tsc --noEmit --project editor/src/prototypes/tsconfig.json
238265

266+
typecheck:portal:
267+
desc: "Typecheck developer portal build variant"
268+
deps: [install]
269+
cmds:
270+
- npx tsc --noEmit --project portal/tsconfig.json
271+
272+
typecheck:shared:
273+
desc: "Typecheck the shared design system"
274+
deps: [install]
275+
cmds:
276+
- npx tsc --noEmit --project shared/tsconfig.json
277+
239278
typecheck:all:
240279
desc: "Typecheck all build variants"
241280
cmds:
@@ -245,6 +284,8 @@ tasks:
245284
- task: typecheck:desktop
246285
- task: typecheck:scripts
247286
- task: typecheck:prototypes
287+
- task: typecheck:portal
288+
- task: typecheck:shared
248289

249290
# ============================================================
250291
# Quality Gate
@@ -265,7 +306,9 @@ tasks:
265306
- task: lint
266307
- task: format:check
267308
- task: build
309+
- task: build:portal
268310
- task: test
311+
- task: storybook:build
269312

270313
# ============================================================
271314
# Test

LICENSE

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@ if that directory exists, is licensed under the license defined in "app/propriet
1010
if that directory exists, is licensed under the license defined in "app/saas/LICENSE".
1111
* All content that resides under the "engine/" directory of this repository,
1212
if that directory exists, is licensed under the license defined in "engine/LICENSE".
13-
* All content that resides under the "frontend/src/proprietary/" directory of this repository,
14-
if that directory exists, is licensed under the license defined in "frontend/src/proprietary/LICENSE".
15-
* All content that resides under the "frontend/src/desktop/" directory of this repository,
16-
if that directory exists, is licensed under the license defined in "frontend/src/desktop/LICENSE".
17-
* All content that resides under the "frontend/src/saas/" directory of this repository,
18-
if that directory exists, is licensed under the license defined in "frontend/src/saas/LICENSE".
19-
* All content that resides under the "frontend/src/prototypes/" directory of this repository,
20-
if that directory exists, is licensed under the license defined in "frontend/src/prototypes/LICENSE".
13+
* All content that resides under the "frontend/editor/src/proprietary/" directory of this repository,
14+
if that directory exists, is licensed under the license defined in "frontend/editor/src/proprietary/LICENSE".
15+
* All content that resides under the "frontend/editor/src/desktop/" directory of this repository,
16+
if that directory exists, is licensed under the license defined in "frontend/editor/src/desktop/LICENSE".
17+
* All content that resides under the "frontend/editor/src/saas/" directory of this repository,
18+
if that directory exists, is licensed under the license defined in "frontend/editor/src/saas/LICENSE".
19+
* All content that resides under the "frontend/editor/src/prototypes/" directory of this repository,
20+
if that directory exists, is licensed under the license defined in "frontend/editor/src/prototypes/LICENSE".
21+
* All content that resides under the "frontend/portal/" directory of this repository,
22+
if that directory exists, is licensed under the license defined in "frontend/portal/LICENSE".
2123
* Content outside of the above mentioned directories or restrictions above is
2224
available under the MIT License as defined below.
2325

docker/frontend/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ RUN npm ci
1313
COPY frontend .
1414

1515
# Build the application (vite root is editor/, output lands in editor/dist/)
16-
RUN npx vite --root editor build
16+
RUN npx vite build editor
1717

1818
# Production stage
1919
FROM nginx:alpine@sha256:b0f7830b6bfaa1258f45d94c240ab668ced1b3651c8a222aefe6683447c7bf55

frontend/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
# production
1212
/build
1313
/dist
14+
/dist-portal
15+
/storybook-static
1416

1517
/editor/build
1618
/editor/dist

frontend/.prettierignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ editor/src-tauri/**/target/
88
editor/src-tauri/gen/
99
node_modules/
1010
editor/public/vendor/
11+
# Auto-generated by MSW (`msw init`); regenerated verbatim, not hand-formatted.
12+
portal/public/mockServiceWorker.js
1113
editor/public/pdfjs*/
1214
editor/public/js/thirdParty/
1315
editor/public/css/cookieconsent.css

frontend/.storybook/main.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { resolve } from "node:path";
2+
import type { StorybookConfig } from "@storybook/react-vite";
3+
4+
/**
5+
* Storybook 9 ships essentials, interactions, and docs as built-ins, so the
6+
* addon list is just the extras we want: theme switching + a11y auditing.
7+
*
8+
* Story files live next to their components in shared/ and portal/src/.
9+
* MDX docs pages live in portal/src/docs/.
10+
*/
11+
const config: StorybookConfig = {
12+
stories: [
13+
"../portal/src/**/*.mdx",
14+
"../portal/src/**/*.stories.@(ts|tsx)",
15+
"../shared/**/*.mdx",
16+
"../shared/**/*.stories.@(ts|tsx)",
17+
],
18+
addons: ["@storybook/addon-themes", "@storybook/addon-a11y"],
19+
framework: {
20+
name: "@storybook/react-vite",
21+
options: {},
22+
},
23+
typescript: {
24+
reactDocgen: "react-docgen-typescript",
25+
},
26+
// Serve the MSW worker file from portal/public so Storybook can intercept
27+
// network calls the same way the dev portal does.
28+
staticDirs: ["../portal/public"],
29+
viteFinal: async (config) => {
30+
// Wire @portal/* and @shared/* aliases directly on the Storybook bundler so
31+
// story imports resolve without needing the portal's vite config.
32+
config.resolve = config.resolve ?? {};
33+
config.resolve.alias = {
34+
...(config.resolve.alias ?? {}),
35+
"@portal": resolve(__dirname, "../portal/src"),
36+
"@shared": resolve(__dirname, "../shared"),
37+
};
38+
return config;
39+
},
40+
};
41+
42+
export default config;

frontend/.storybook/preview.tsx

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// Storybook compiles .storybook/* with the classic JSX runtime, so the JSX in
2+
// the decorators below transpiles to React.createElement and needs React in
3+
// scope. (The app + story files use the automatic runtime via the portal vite
4+
// config; this import is specifically for the preview config file.)
5+
import React, { useEffect } from "react";
6+
import type { Decorator, Preview } from "@storybook/react-vite";
7+
import { initialize, mswLoader } from "msw-storybook-addon";
8+
import { MemoryRouter } from "react-router-dom";
9+
import { withThemeByDataAttribute } from "@storybook/addon-themes";
10+
import { MantineProvider } from "@mantine/core";
11+
12+
// Reference React so the import isn't dropped as unused by the bundler — the
13+
// classic runtime needs it present even though it's not named in the JSX.
14+
void React;
15+
16+
import { TierProvider, type Tier } from "@portal/contexts/TierContext";
17+
import { ThemeProvider } from "@portal/contexts/ThemeContext";
18+
import { UIProvider } from "@portal/contexts/UIContext";
19+
import { mantineTheme } from "@portal/theme/mantineTheme";
20+
import { handlers } from "@portal/mocks/handlers";
21+
22+
import "@mantine/core/styles.css";
23+
import "@shared/tokens/tokens.css";
24+
import "@shared/tokens/base.css";
25+
26+
// Start MSW once. Storybook runs in a browser so this uses the service worker.
27+
initialize({ onUnhandledRequest: "bypass" }, handlers);
28+
29+
/**
30+
* Bridge between Storybook's `tier` global toolbar and the actual TierProvider.
31+
* Without this the toolbar would just change a label; with it, every story
32+
* that calls useTier() reflects the active toolbar value.
33+
*/
34+
function TierBridge({
35+
tier,
36+
children,
37+
}: {
38+
tier: Tier;
39+
children: React.ReactNode;
40+
}) {
41+
return <TierProvider initialTier={tier}>{children}</TierProvider>;
42+
}
43+
44+
/** Forces the TierProvider to re-mount whenever the toolbar tier changes. */
45+
function TierKey({
46+
tier,
47+
children,
48+
}: {
49+
tier: Tier;
50+
children: React.ReactNode;
51+
}) {
52+
return (
53+
<TierBridge key={tier} tier={tier}>
54+
{children}
55+
</TierBridge>
56+
);
57+
}
58+
59+
/** Keeps useTheme() and the data-theme attribute in sync. */
60+
function ThemeWatcher() {
61+
useEffect(() => {
62+
// The addon-themes decorator already sets data-theme on <html>.
63+
// We just read it on mount so ThemeProvider picks it up.
64+
}, []);
65+
return null;
66+
}
67+
68+
const withProviders: Decorator = (Story, context) => {
69+
const tier = (context.globals.tier as Tier) ?? "pro";
70+
// withThemeByDataAttribute exposes the toolbar theme as the `theme` global.
71+
// Bind Mantine's color scheme to it so Mantine chrome (inputs, focus rings,
72+
// default surfaces) follows the dark toggle alongside the SUI CSS variables.
73+
// The global initialises to "" (before any toolbar interaction), so treat
74+
// anything that isn't "dark" as light — matching the addon's own
75+
// `selected || defaultTheme` fallback where defaultTheme is light.
76+
const colorScheme = context.globals.theme === "dark" ? "dark" : "light";
77+
return (
78+
<MemoryRouter initialEntries={["/"]}>
79+
<ThemeProvider>
80+
<MantineProvider theme={mantineTheme} forceColorScheme={colorScheme}>
81+
<TierKey tier={tier}>
82+
<UIProvider>
83+
<ThemeWatcher />
84+
<Story />
85+
</UIProvider>
86+
</TierKey>
87+
</MantineProvider>
88+
</ThemeProvider>
89+
</MemoryRouter>
90+
);
91+
};
92+
93+
const preview: Preview = {
94+
loaders: [mswLoader],
95+
parameters: {
96+
layout: "padded",
97+
controls: {
98+
matchers: { color: /(background|color)$/i, date: /Date$/i },
99+
},
100+
backgrounds: {
101+
default: "app",
102+
values: [
103+
{ name: "app", value: "var(--color-bg)" },
104+
{ name: "surface", value: "var(--color-surface)" },
105+
],
106+
},
107+
a11y: {
108+
// Run axe automatically against the story root; violations show in the
109+
// Accessibility panel. `context` replaced `element` in addon-a11y 9.x.
110+
context: "#storybook-root",
111+
config: {},
112+
options: {},
113+
test: "todo",
114+
},
115+
},
116+
globalTypes: {
117+
tier: {
118+
name: "Tier",
119+
description: "Subscription tier — drives useTier() everywhere",
120+
defaultValue: "pro",
121+
toolbar: {
122+
icon: "star",
123+
items: [
124+
{ value: "free", title: "Free" },
125+
{ value: "pro", title: "Pay-as-you-go" },
126+
{ value: "enterprise", title: "Enterprise" },
127+
],
128+
dynamicTitle: true,
129+
},
130+
},
131+
},
132+
decorators: [
133+
withProviders,
134+
withThemeByDataAttribute({
135+
themes: { light: "light", dark: "dark" },
136+
defaultTheme: "light",
137+
attributeName: "data-theme",
138+
}),
139+
],
140+
};
141+
142+
export default preview;

frontend/editor/scripts/tsconfig.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,5 @@
55
"moduleResolution": "node16",
66
"noEmit": true
77
},
8-
"include": ["./**/*.ts", "./**/*.mts"],
9-
"exclude": []
8+
"include": ["./**/*.ts", "./**/*.mts"]
109
}

0 commit comments

Comments
 (0)