@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ramme-io/create-app",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "description": "The official CLI to create Ramme applications.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -1,3 +1,4 @@
1
+ # Version: 2026-05
1
2
  # ═══════════════════════════════════════════════════════════════════════
2
3
  # .rammerules — Ramme App Agent Enforcement Manifest
3
4
  # Distributed via: @ramme-io/create-app
@@ -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.
@@ -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/config/sitemap.ts` — The Skeleton
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 this single file.
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/core/component-registry.tsx` — The Wiring
41
+ ### `src/config/component-registry.tsx` — The Wiring
42
42
 
43
- Maps string-based component names (from the sitemap) to actual React components via lazy imports. This is what enables the Kernel to dynamically render pages without hardcoded route definitions.
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/pages/` — Your Pages
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/pages/` (e.g., `MyPage.tsx`).
64
- 2. Add a lazy import to `src/core/component-registry.tsx`.
65
- 3. Add an entry to `src/config/sitemap.ts` with the path, label, and component name.
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}]]}
@@ -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 . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
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
- "@google/generative-ai": "^0.24.1",
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",
@@ -7,7 +7,6 @@ import {
7
7
  Select,
8
8
  Modal,
9
9
  Drawer,
10
- Icon,
11
10
  Accordion,
12
11
  AccordionItem,
13
12
  useTheme,
@@ -3,7 +3,6 @@ import React, { useState } from 'react';
3
3
  import {
4
4
  Card,
5
5
  Button,
6
- Input,
7
6
  Stepper,
8
7
  Tabs,
9
8
  TabPanel,
@@ -7,7 +7,6 @@ import {
7
7
  useTheme,
8
8
  ButtonGroup,
9
9
  Button,
10
- Icon,
11
10
  } from '@ramme-io/ui';
12
11
  import type { ThemeName } from '@ramme-io/ui';
13
12
 
@@ -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
+ });