@ramme-io/create-app 2.2.0 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/.rammerules +1 -0
- package/template/AGENTS.md +12 -0
- package/template/README.md +9 -9
- package/template/docs/BAD_EXAMPLES.md +69 -0
- package/template/docs/recipes/add-a-chart.md +34 -0
- package/template/docs/recipes/add-a-crud-page.md +40 -0
- package/template/docs/skus.md +12 -0
- package/template/eslint.config.js +7 -3
- package/template/node_modules/.bin/eslint +17 -0
- package/template/node_modules/.vite/vitest/results.json +1 -0
- package/template/package.json +10 -5
- package/template/src/features/styleguide/sections/layout/LayoutSection.tsx +0 -1
- package/template/src/features/styleguide/sections/navigation/NavigationSection.tsx +0 -1
- package/template/src/features/styleguide/sections/theming/ThemingSection.tsx +0 -1
- package/template/src/templates/README.md +1 -0
- package/template/src/tests/smoke.test.tsx +70 -0
package/package.json
CHANGED
package/template/.rammerules
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Agent-Ready Architecture
|
|
2
|
+
|
|
3
|
+
- **Mandate pnpm usage:** Referencing only-allow pnpm.
|
|
4
|
+
- **Laws of the Project:** See [.rammerules](./.rammerules).
|
|
5
|
+
|
|
6
|
+
## Route Addition Flow
|
|
7
|
+
1. Create feature page.
|
|
8
|
+
2. Update `component-registry.tsx`.
|
|
9
|
+
3. Update `dashboard.sitemap.ts`.
|
|
10
|
+
|
|
11
|
+
## Canonical Layout Exemplar
|
|
12
|
+
Refer to [MainDashboard.tsx](./src/templates/dashboard/MainDashboard.tsx) as the canonical layout exemplar.
|
package/template/README.md
CHANGED
|
@@ -34,17 +34,17 @@ Ramme apps are driven by three configuration files. You rarely need to touch the
|
|
|
34
34
|
|
|
35
35
|
Your app's global identity. Define the title, description, default theme, and top-level settings here. The Kernel reads this on boot to configure the application shell.
|
|
36
36
|
|
|
37
|
-
### `src/
|
|
37
|
+
### `src/templates/` — The Skeleton
|
|
38
38
|
|
|
39
|
-
The declarative routing and navigation structure. Each entry defines a page path, its label, icon, and which component to render. The Kernel generates all routes and navigation menus from
|
|
39
|
+
The declarative routing and navigation structure via sitemap files (e.g., `dashboard.sitemap.ts`). Each entry defines a page path, its label, icon, and which component to render. The Kernel generates all routes and navigation menus from these files.
|
|
40
40
|
|
|
41
|
-
### `src/
|
|
41
|
+
### `src/config/component-registry.tsx` — The Wiring
|
|
42
42
|
|
|
43
|
-
Maps string-based component names (from the
|
|
43
|
+
Maps string-based component names (from the sitemaps) to actual React components via lazy imports. This is what enables the Kernel to dynamically render pages without hardcoded route definitions.
|
|
44
44
|
|
|
45
|
-
### `src/
|
|
45
|
+
### `src/features/` — Your Features
|
|
46
46
|
|
|
47
|
-
Your actual page components. Create new files here and register them in the sitemap and component registry to add new routes.
|
|
47
|
+
Your actual page components and features. Create new files here and register them in the sitemap and component registry to add new routes.
|
|
48
48
|
|
|
49
49
|
### `src/data/` — Mock Data
|
|
50
50
|
|
|
@@ -60,9 +60,9 @@ Update your brand colors in `tailwind.config.js` by extending the Ramme preset,
|
|
|
60
60
|
|
|
61
61
|
### Adding a New Page
|
|
62
62
|
|
|
63
|
-
1. Create a new component in `src/
|
|
64
|
-
2. Add a lazy import to `src/
|
|
65
|
-
3. Add an entry to `src/
|
|
63
|
+
1. Create a new component in `src/features/` (e.g., `MyPage.tsx`).
|
|
64
|
+
2. Add a lazy import to `src/config/component-registry.tsx`.
|
|
65
|
+
3. Add an entry to the appropriate sitemap in `src/templates/` with the path, label, and component name.
|
|
66
66
|
4. The Kernel handles the rest — routing, navigation, and code-splitting are automatic.
|
|
67
67
|
|
|
68
68
|
---
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Anti-Patterns vs. The Framework Way
|
|
2
|
+
|
|
3
|
+
This document highlights common anti-patterns in UI and state implementation, contrasting them with the approved Ramme framework patterns.
|
|
4
|
+
|
|
5
|
+
## 1. UI Elements: Raw HTML vs. UI Primitives
|
|
6
|
+
|
|
7
|
+
**❌ Anti-pattern (Raw HTML):**
|
|
8
|
+
```tsx
|
|
9
|
+
// Do NOT use raw HTML elements with custom styling for standard UI components.
|
|
10
|
+
<button className="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded">
|
|
11
|
+
Save Changes
|
|
12
|
+
</button>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**✅ Framework Way:**
|
|
16
|
+
```tsx
|
|
17
|
+
// ALWAYS use the `@ramme-io/ui` primitives.
|
|
18
|
+
import { Button } from '@ramme-io/ui';
|
|
19
|
+
|
|
20
|
+
<Button variant="primary">
|
|
21
|
+
Save Changes
|
|
22
|
+
</Button>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 2. Layout Injection: Hardcoded Sizing vs. Layout Tokens
|
|
26
|
+
|
|
27
|
+
**❌ Anti-pattern (Layout Injection):**
|
|
28
|
+
```tsx
|
|
29
|
+
// Do NOT hardcode fixed sizes or absolute positions within AppShell slots.
|
|
30
|
+
<div className="h-screen w-full fixed top-0 left-0 bg-white">
|
|
31
|
+
<div className="h-[60px] w-full bg-gray-100">Header</div>
|
|
32
|
+
<div className="mt-[60px]">Content</div>
|
|
33
|
+
</div>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**✅ Framework Way:**
|
|
37
|
+
```tsx
|
|
38
|
+
// ALWAYS use the structured layouts (e.g. DashboardLayout) and let the AppShell handle constraints.
|
|
39
|
+
import DashboardLayout from '@/templates/dashboard/DashboardLayout';
|
|
40
|
+
import { PageTemplate } from '@/templates/PageTemplate';
|
|
41
|
+
|
|
42
|
+
<PageTemplate title="My Feature">
|
|
43
|
+
<div>Content flows naturally inside the constrained layout area.</div>
|
|
44
|
+
</PageTemplate>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 3. State Management: Inline Business Logic vs. Domain Hooks
|
|
48
|
+
|
|
49
|
+
**❌ Anti-pattern (Inline Business Logic):**
|
|
50
|
+
```tsx
|
|
51
|
+
// Do NOT hardcode business logic, API calls, or heavy math directly inside UI components.
|
|
52
|
+
export function UserProfile() {
|
|
53
|
+
const handleSave = async () => {
|
|
54
|
+
// 50 lines of complex data formatting and fetching logic here...
|
|
55
|
+
await fetch('/api/user', { ... });
|
|
56
|
+
};
|
|
57
|
+
return <Button onClick={handleSave}>Save</Button>;
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**✅ Framework Way:**
|
|
62
|
+
```tsx
|
|
63
|
+
// ALWAYS use the dedicated hooks or services layer (`@ramme-io/kernel` or local feature hooks).
|
|
64
|
+
import { useUserProfile } from '@/features/users/hooks/useUserProfile';
|
|
65
|
+
|
|
66
|
+
export function UserProfile() {
|
|
67
|
+
const { saveUser } = useUserProfile();
|
|
68
|
+
return <Button onClick={saveUser}>Save</Button>;
|
|
69
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Recipe: Add a Chart
|
|
2
|
+
|
|
3
|
+
1. **Import Recharts Components**
|
|
4
|
+
```tsx
|
|
5
|
+
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
2. **Prepare Data**
|
|
9
|
+
```tsx
|
|
10
|
+
const data = [
|
|
11
|
+
{ name: 'Jan', value: 400 },
|
|
12
|
+
{ name: 'Feb', value: 300 },
|
|
13
|
+
{ name: 'Mar', value: 600 },
|
|
14
|
+
];
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
3. **Render the Chart**
|
|
18
|
+
Use a `ResponsiveContainer` to ensure it fits the layout properly:
|
|
19
|
+
```tsx
|
|
20
|
+
export function MyChart() {
|
|
21
|
+
return (
|
|
22
|
+
<div className="h-64 w-full">
|
|
23
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
24
|
+
<LineChart data={data}>
|
|
25
|
+
<CartesianGrid strokeDasharray="3 3" />
|
|
26
|
+
<XAxis dataKey="name" />
|
|
27
|
+
<YAxis />
|
|
28
|
+
<Tooltip />
|
|
29
|
+
<Line type="monotone" dataKey="value" stroke="#8884d8" />
|
|
30
|
+
</LineChart>
|
|
31
|
+
</ResponsiveContainer>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Recipe: Add a CRUD Page
|
|
2
|
+
|
|
3
|
+
1. **Create the Feature File**
|
|
4
|
+
Create `src/features/my-feature/pages/MyFeaturePage.tsx`:
|
|
5
|
+
```tsx
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { PageTemplate } from '@/templates/PageTemplate';
|
|
8
|
+
|
|
9
|
+
export default function MyFeaturePage() {
|
|
10
|
+
return (
|
|
11
|
+
<PageTemplate title="My Feature" description="Manage my feature.">
|
|
12
|
+
<div className="p-4">
|
|
13
|
+
{/* Add your SmartTable or AutoForm here */}
|
|
14
|
+
</div>
|
|
15
|
+
</PageTemplate>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
2. **Register the Component**
|
|
21
|
+
In `src/config/component-registry.tsx`, add the lazy import:
|
|
22
|
+
```tsx
|
|
23
|
+
export const ComponentRegistry = {
|
|
24
|
+
// ...existing routes
|
|
25
|
+
MyFeaturePage: lazy(() => import('@/features/my-feature/pages/MyFeaturePage')),
|
|
26
|
+
};
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
3. **Add to Sitemap**
|
|
30
|
+
In `src/templates/dashboard/dashboard.sitemap.ts`:
|
|
31
|
+
```ts
|
|
32
|
+
export const dashboardSitemap = [
|
|
33
|
+
// ...existing
|
|
34
|
+
{
|
|
35
|
+
path: '/my-feature',
|
|
36
|
+
label: 'My Feature',
|
|
37
|
+
icon: 'Database',
|
|
38
|
+
component: 'MyFeaturePage',
|
|
39
|
+
}
|
|
40
|
+
];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Discoverability Index: Core Layout SKUs
|
|
2
|
+
|
|
3
|
+
The following components represent the core application shells and layouts within the starter blueprint. Use these paths when configuring routing or creating new structural features.
|
|
4
|
+
|
|
5
|
+
| SKU ID | Import Path | Description |
|
|
6
|
+
|--------|-------------|-------------|
|
|
7
|
+
| `MainDashboard` | `@/features/dashboard/MainDashboard` | The canonical dashboard view providing primary metrics and top-level entry points. |
|
|
8
|
+
| `DashboardLayout` | `@/templates/dashboard/DashboardLayout` | The 5-part chassis structure for main application content with sidebar, header, and toolbar. |
|
|
9
|
+
| `SettingsLayout` | `@/templates/settings/SettingsLayout` | A focused layout for application and user settings with an AppShell-wrapped configuration. |
|
|
10
|
+
| `DocsLayout` | `@/templates/docs/DocsLayout` | A layout optimized for reading documentation and styleguides. |
|
|
11
|
+
| `AppChromeHost` | `@/components/layout/AppChromeHost` | The root orchestrator that controls navigation bars and global overlay rendering. |
|
|
12
|
+
| `PageTemplate` | `@/templates/PageTemplate` | The standard container wrapper for inner pages to maintain consistent padding and headers. |
|
|
@@ -11,13 +11,17 @@ export default tseslint.config([
|
|
|
11
11
|
files: ['**/*.{ts,tsx}'],
|
|
12
12
|
extends: [
|
|
13
13
|
js.configs.recommended,
|
|
14
|
-
tseslint.configs.recommended,
|
|
15
|
-
reactHooks.configs['recommended-latest'],
|
|
16
|
-
reactRefresh.configs.vite,
|
|
14
|
+
...tseslint.configs.recommended,
|
|
17
15
|
],
|
|
18
16
|
languageOptions: {
|
|
19
17
|
ecmaVersion: 2020,
|
|
20
18
|
globals: globals.browser,
|
|
21
19
|
},
|
|
20
|
+
rules: {
|
|
21
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
22
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
23
|
+
'@typescript-eslint/ban-ts-comment': 'off',
|
|
24
|
+
'prefer-const': 'off'
|
|
25
|
+
}
|
|
22
26
|
},
|
|
23
27
|
])
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
|
|
6
|
+
esac
|
|
7
|
+
|
|
8
|
+
if [ -z "$NODE_PATH" ]; then
|
|
9
|
+
export NODE_PATH="/Users/ramme/ramme-monorepo/node_modules/.pnpm/eslint@9.39.4_jiti@1.21.7/node_modules/eslint/bin/node_modules:/Users/ramme/ramme-monorepo/node_modules/.pnpm/eslint@9.39.4_jiti@1.21.7/node_modules/eslint/node_modules:/Users/ramme/ramme-monorepo/node_modules/.pnpm/eslint@9.39.4_jiti@1.21.7/node_modules:/Users/ramme/ramme-monorepo/node_modules/.pnpm/node_modules"
|
|
10
|
+
else
|
|
11
|
+
export NODE_PATH="/Users/ramme/ramme-monorepo/node_modules/.pnpm/eslint@9.39.4_jiti@1.21.7/node_modules/eslint/bin/node_modules:/Users/ramme/ramme-monorepo/node_modules/.pnpm/eslint@9.39.4_jiti@1.21.7/node_modules/eslint/node_modules:/Users/ramme/ramme-monorepo/node_modules/.pnpm/eslint@9.39.4_jiti@1.21.7/node_modules:/Users/ramme/ramme-monorepo/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
12
|
+
fi
|
|
13
|
+
if [ -x "$basedir/node" ]; then
|
|
14
|
+
exec "$basedir/node" "$basedir/../eslint/bin/eslint.js" "$@"
|
|
15
|
+
else
|
|
16
|
+
exec node "$basedir/../eslint/bin/eslint.js" "$@"
|
|
17
|
+
fi
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":"2.1.9","results":[[":src/hooks/__tests__/useStudioHotkeys.test.ts",{"duration":12.74512500000003,"failed":false}],[":src/tests/smoke.test.tsx",{"duration":80.62616600000001,"failed":false}]]}
|
package/template/package.json
CHANGED
|
@@ -5,30 +5,33 @@
|
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "vite",
|
|
8
|
+
"dev:agent": "pnpm build && pnpm lint && pnpm test",
|
|
8
9
|
"build": "tsc && vite build",
|
|
9
|
-
"lint": "eslint . --
|
|
10
|
+
"lint": "eslint . --max-warnings 0",
|
|
10
11
|
"preview": "vite preview",
|
|
11
12
|
"test": "vitest run",
|
|
12
|
-
"test:watch": "vitest"
|
|
13
|
+
"test:watch": "vitest",
|
|
14
|
+
"preinstall": "npx only-allow pnpm"
|
|
13
15
|
},
|
|
14
16
|
"dependencies": {
|
|
15
17
|
"@ai-sdk/google": "^0.0.10",
|
|
16
|
-
"@
|
|
18
|
+
"@eslint/js": "^10.0.1",
|
|
17
19
|
"@radix-ui/react-slot": "^1.2.4",
|
|
18
20
|
"@ramme-io/kernel": "workspace:*",
|
|
19
21
|
"@ramme-io/shared": "workspace:*",
|
|
20
22
|
"@ramme-io/ui": "workspace:*",
|
|
21
23
|
"ably": "^2.21.0",
|
|
22
|
-
"ag-charts-community": "^13.0.0",
|
|
23
|
-
"ag-charts-react": "^13.0.0",
|
|
24
24
|
"ag-grid-community": "^31.3.1",
|
|
25
25
|
"ag-grid-enterprise": "^31.3.1",
|
|
26
26
|
"ag-grid-react": "^31.3.1",
|
|
27
27
|
"ai": "^3.0.0",
|
|
28
28
|
"class-variance-authority": "^0.7.1",
|
|
29
29
|
"clsx": "^2.1.1",
|
|
30
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
31
|
+
"eslint-plugin-react-refresh": "^0.5.2",
|
|
30
32
|
"framer-motion": "^11.0.0",
|
|
31
33
|
"fs-extra": "^11.2.0",
|
|
34
|
+
"globals": "^17.6.0",
|
|
32
35
|
"lucide-react": "^0.454.0",
|
|
33
36
|
"mqtt": "^5.14.1",
|
|
34
37
|
"react": "^18.3.1",
|
|
@@ -41,6 +44,7 @@
|
|
|
41
44
|
"recharts": "^2.12.7",
|
|
42
45
|
"remark-gfm": "^4.0.1",
|
|
43
46
|
"tailwind-merge": "^2.5.2",
|
|
47
|
+
"typescript-eslint": "^8.59.4",
|
|
44
48
|
"zod": "^3.23.8",
|
|
45
49
|
"zustand": "^5.0.0"
|
|
46
50
|
},
|
|
@@ -53,6 +57,7 @@
|
|
|
53
57
|
"@vitejs/plugin-react": "^4.3.1",
|
|
54
58
|
"@vitest/ui": "^2",
|
|
55
59
|
"autoprefixer": "^10.4.19",
|
|
60
|
+
"eslint": "^9.39.4",
|
|
56
61
|
"jsdom": "^29.0.0",
|
|
57
62
|
"postcss": "^8.4.39",
|
|
58
63
|
"tailwindcss": "^3.4.4",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Templates define the top-level app shells/layouts, while features define the domain-specific business logic consumed by those templates.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
3
|
+
import { render } from '@testing-library/react';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { BrowserRouter } from 'react-router-dom';
|
|
6
|
+
import { ManifestProvider, AuthProvider, MqttProvider } from '@ramme-io/kernel';
|
|
7
|
+
import { ThemeProvider, ToastProvider } from '@ramme-io/ui';
|
|
8
|
+
import App from '../App';
|
|
9
|
+
import { COMPONENT_REGISTRY } from '../config/component-registry';
|
|
10
|
+
import { AppConfigProvider } from '../features/config/AppConfigContext';
|
|
11
|
+
import { appManifest } from '../config/app.manifest';
|
|
12
|
+
|
|
13
|
+
describe('Smoke Test', () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
Object.defineProperty(window, 'localStorage', {
|
|
16
|
+
value: {
|
|
17
|
+
getItem: () => null,
|
|
18
|
+
setItem: () => null,
|
|
19
|
+
removeItem: () => null,
|
|
20
|
+
},
|
|
21
|
+
writable: true
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
Object.defineProperty(window, 'scrollTo', { value: () => {}, writable: true });
|
|
25
|
+
Object.defineProperty(Element.prototype, 'scrollTo', { value: () => {}, writable: true });
|
|
26
|
+
Object.defineProperty(Element.prototype, 'scrollIntoView', { value: () => {}, writable: true });
|
|
27
|
+
|
|
28
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
29
|
+
writable: true,
|
|
30
|
+
value: (query: string) => ({
|
|
31
|
+
matches: false,
|
|
32
|
+
media: query,
|
|
33
|
+
onchange: null,
|
|
34
|
+
addListener: () => {}, // deprecated
|
|
35
|
+
removeListener: () => {}, // deprecated
|
|
36
|
+
addEventListener: () => {},
|
|
37
|
+
removeEventListener: () => {},
|
|
38
|
+
dispatchEvent: () => false,
|
|
39
|
+
}),
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should successfully import the component registry', () => {
|
|
44
|
+
expect(COMPONENT_REGISTRY).toBeDefined();
|
|
45
|
+
expect(Object.keys(COMPONENT_REGISTRY).length).toBeGreaterThan(0);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should render the App component without crashing', () => {
|
|
49
|
+
// We only need to check if it renders. Since App uses BrowserRouter and other providers,
|
|
50
|
+
// a basic render is sufficient as a smoke test.
|
|
51
|
+
const { container } = render(
|
|
52
|
+
<BrowserRouter>
|
|
53
|
+
<ManifestProvider manifest={appManifest}>
|
|
54
|
+
<AppConfigProvider>
|
|
55
|
+
<ThemeProvider>
|
|
56
|
+
<ToastProvider>
|
|
57
|
+
<AuthProvider>
|
|
58
|
+
<MqttProvider>
|
|
59
|
+
<App />
|
|
60
|
+
</MqttProvider>
|
|
61
|
+
</AuthProvider>
|
|
62
|
+
</ToastProvider>
|
|
63
|
+
</ThemeProvider>
|
|
64
|
+
</AppConfigProvider>
|
|
65
|
+
</ManifestProvider>
|
|
66
|
+
</BrowserRouter>
|
|
67
|
+
);
|
|
68
|
+
expect(container).toBeDefined();
|
|
69
|
+
});
|
|
70
|
+
});
|