@ramme-io/create-app 2.0.0 → 2.0.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.
Files changed (32) hide show
  1. package/README.md +2 -2
  2. package/index.js +1 -1
  3. package/package.json +1 -1
  4. package/template/index.html +1 -1
  5. package/template/package.json +14 -5
  6. package/template/public/_redirects +1 -0
  7. package/template/src/App.tsx +3 -2
  8. package/template/src/components/AppHeader.tsx +5 -0
  9. package/template/src/components/ScrollToTop.tsx +6 -6
  10. package/template/src/features/ai/pages/AiChat.tsx +136 -37
  11. package/template/src/features/auth/AuthContext.tsx +16 -6
  12. package/template/src/features/config/AppConfigContext.tsx +2 -2
  13. package/template/src/features/docs/pages/EdgeTelemetryDemo.tsx +149 -0
  14. package/template/src/features/onboarding/pages/AboutRamme.tsx +12 -12
  15. package/template/src/features/onboarding/pages/PrototypeGallery.tsx +8 -6
  16. package/template/src/features/onboarding/pages/RammeFeatures.tsx +18 -17
  17. package/template/src/features/onboarding/pages/RammeTutorial.tsx +20 -11
  18. package/template/src/features/onboarding/pages/Welcome.tsx +6 -6
  19. package/template/src/features/styleguide/sections/tables/TablesSection.tsx +25 -5
  20. package/template/src/features/theme/pages/ThemeCustomizerPage.tsx +344 -256
  21. package/template/src/features/theme/utils/ThemeGenerator.logic.ts +587 -0
  22. package/template/src/hooks/__tests__/useStudioHotkeys.test.ts +100 -0
  23. package/template/src/hooks/useStudioHotkeys.ts +36 -0
  24. package/template/src/index.css +91 -1
  25. package/template/src/main.tsx +44 -2
  26. package/template/src/templates/dashboard/DashboardLayout.tsx +6 -1
  27. package/template/src/templates/dashboard/dashboard.sitemap.ts +1 -1
  28. package/template/src/templates/docs/docs.sitemap.ts +8 -0
  29. package/template/src/templates/settings/SettingsLayout.tsx +13 -26
  30. package/template/src/test/setup.ts +1 -0
  31. package/template/tsconfig.app.json +1 -0
  32. package/template/vite.config.ts +7 -0
package/README.md CHANGED
@@ -15,9 +15,9 @@ This CLI scaffolds a production-ready React application with:
15
15
  Get started in seconds. No global installation required.
16
16
 
17
17
  ```bash
18
- npm create @ramme-io/app@latest my-app
18
+ npm create @ramme-io/app my-app
19
19
  # or
20
- npx @ramme-io/create-app@latest my-app
20
+ npm create @ramme-io/app my-app
21
21
  ```
22
22
 
23
23
  You will be prompted to name your project.
package/index.js CHANGED
@@ -19,7 +19,7 @@ const projectName = process.argv[2];
19
19
  if (!projectName) {
20
20
  console.error('❌ Error: Please specify the project directory.');
21
21
  console.log('\n📖 Usage:');
22
- console.log(' npm create @ramme-io/app@latest <project-name>\n');
22
+ console.log(' npm create @ramme-io/app <project-name>\n');
23
23
  process.exit(1);
24
24
  }
25
25
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ramme-io/create-app",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "The official CLI to create Ramme applications.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <link rel="icon" type="image/svg+xml" href="/orange.png" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>Ramme - App Starter</title>
7
+ <title>Ramme | The High-Fidelity Creator Framework</title>
8
8
  </head>
9
9
  <body>
10
10
  <div id="root"></div>
@@ -7,14 +7,18 @@
7
7
  "dev": "vite",
8
8
  "build": "tsc && vite build",
9
9
  "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10
- "preview": "vite preview"
10
+ "preview": "vite preview",
11
+ "test": "vitest run",
12
+ "test:watch": "vitest"
11
13
  },
12
14
  "dependencies": {
13
15
  "@ai-sdk/google": "^0.0.10",
14
16
  "@google/generative-ai": "^0.24.1",
15
17
  "@radix-ui/react-slot": "^1.2.4",
16
- "@ramme-io/kernel": "^2.0.0",
17
- "@ramme-io/ui": "^2.0.0",
18
+ "@ramme-io/kernel": "workspace:*",
19
+ "@ramme-io/shared": "workspace:*",
20
+ "@ramme-io/ui": "workspace:*",
21
+ "ably": "^2.21.0",
18
22
  "ag-charts-community": "^13.0.0",
19
23
  "ag-charts-react": "^13.0.0",
20
24
  "ag-grid-community": "^31.3.1",
@@ -30,8 +34,8 @@
30
34
  "react": "^18.3.1",
31
35
  "react-datepicker": "^7.3.0",
32
36
  "react-dom": "^18.3.1",
33
- "react-router-dom": "6.22.3",
34
37
  "react-markdown": "^10.1.0",
38
+ "react-router-dom": "6.22.3",
35
39
  "react-select": "^5.8.0",
36
40
  "react-syntax-highlighter": "^16.1.0",
37
41
  "recharts": "^2.12.7",
@@ -41,14 +45,19 @@
41
45
  "zustand": "^5.0.0"
42
46
  },
43
47
  "devDependencies": {
48
+ "@testing-library/jest-dom": "^6.9.1",
49
+ "@testing-library/react": "^16.3.2",
44
50
  "@types/node": "^20.14.10",
45
51
  "@types/react": "^18.3.3",
46
52
  "@types/react-dom": "^18.3.0",
47
53
  "@vitejs/plugin-react": "^4.3.1",
54
+ "@vitest/ui": "^2",
48
55
  "autoprefixer": "^10.4.19",
56
+ "jsdom": "^29.0.0",
49
57
  "postcss": "^8.4.39",
50
58
  "tailwindcss": "^3.4.4",
51
59
  "typescript": "^5.2.2",
52
- "vite": "^5.3.1"
60
+ "vite": "^5.3.1",
61
+ "vitest": "^2.1.9"
53
62
  }
54
63
  }
@@ -0,0 +1 @@
1
+ /* /index.html 200
@@ -15,8 +15,6 @@ import DashboardLayout from './templates/dashboard/DashboardLayout';
15
15
  import DocsLayout from './templates/docs/DocsLayout';
16
16
  import SettingsLayout from './templates/settings/SettingsLayout';
17
17
 
18
-
19
-
20
18
  // --- SITEMAPS ---
21
19
  import { dashboardSitemap } from './templates/dashboard/dashboard.sitemap';
22
20
  import { docsSitemap } from './templates/docs/docs.sitemap';
@@ -24,6 +22,7 @@ import { settingsSitemap } from './templates/settings/settings.sitemap';
24
22
 
25
23
  // --- UTILS ---
26
24
  import ProtectedRoute from './components/ProtectedRoute';
25
+ import { useStudioHotkeys } from './hooks/useStudioHotkeys';
27
26
  import NotFound from './components/NotFound';
28
27
  import ScrollToTop from './components/ScrollToTop';
29
28
  import HashLinkScroll from './components/HashLinkScroll';
@@ -82,6 +81,7 @@ const generateRoutes = (items: any[]) => {
82
81
  };
83
82
 
84
83
  function App() {
84
+ useStudioHotkeys();
85
85
  const liveDashboardRoutes = useDynamicSitemap(dashboardSitemap);
86
86
 
87
87
  return (
@@ -120,6 +120,7 @@ function App() {
120
120
 
121
121
  {/* Settings */}
122
122
  <Route path="/settings/*" element={<SettingsLayout />}>
123
+ <Route index element={<Navigate to="general" replace />} />
123
124
  {generateRoutes(settingsSitemap)}
124
125
  </Route>
125
126
 
@@ -77,6 +77,11 @@ const AppHeader: React.FC<AppHeaderProps> = ({
77
77
  label="Documents"
78
78
  onClick={() => navigate('/docs')}
79
79
  />
80
+ <ButtonItem
81
+ icon="cpu"
82
+ label="Bodewell"
83
+ onClick={() => navigate('/bodewell/fleet')}
84
+ />
80
85
 
81
86
  <ButtonItem
82
87
  icon="refresh-cw"
@@ -5,12 +5,12 @@ const ScrollToTop = () => {
5
5
  const { pathname } = useLocation();
6
6
 
7
7
  useEffect(() => {
8
- // "instant" prevents the user from seeing the page scroll up
9
- window.scrollTo({
10
- top: 0,
11
- left: 0,
12
- behavior: 'instant',
13
- });
8
+ const container = document.getElementById('main-scroll-container');
9
+ if (container) {
10
+ container.scrollTo({ top: 0, left: 0, behavior: 'instant' as ScrollBehavior });
11
+ } else {
12
+ window.scrollTo({ top: 0, left: 0, behavior: 'instant' as ScrollBehavior });
13
+ }
14
14
  }, [pathname]);
15
15
 
16
16
  return null;
@@ -1,61 +1,160 @@
1
- // ramme-app-starter/template/src/pages/AiChat.tsx
2
- import React, { useState } from 'react';
3
- import {
4
- Card,
5
- PageHeader,
6
- Conversation,
7
- Message,
8
- PromptInput,
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import ReactMarkdown from 'react-markdown';
3
+ import remarkGfm from 'remark-gfm';
4
+ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
5
+ import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
6
+ import {
7
+ Card, Conversation, Message, PromptInput, PageHeader, Icon,
9
8
  } from '@ramme-io/ui';
10
- import { useMockChat } from '../../assistant/useMockChat'; // <--- The new brain
9
+
10
+ const systemInstruction = `
11
+ You are the Ramme OS Assistant, part of the internal dev team.
12
+ Ramme is a high-performance, browser-native modular web-OS (Kernel + UI).
13
+ It is NOT the Instagram wrapper by Niklas Higle.
14
+ Answer as a Senior Engineer helping with a monorepo project.
15
+ Use numbered lists for steps and code blocks for commands.
16
+ `;
11
17
 
12
18
  const AiChat: React.FC = () => {
13
- const { messages, isLoading, sendMessage } = useMockChat();
14
19
  const [input, setInput] = useState('');
20
+ const [isLoading, setIsLoading] = useState(false);
21
+ const [apiHistory, setApiHistory] = useState<any[]>([]);
22
+ const scrollRef = useRef<HTMLDivElement>(null);
23
+ const [messages, setMessages] = useState([
24
+ { id: 'init', author: 'AI Agent', content: 'Ramme OS Assistant online. How can I help you build today?', isUser: false }
25
+ ]);
26
+
27
+ useEffect(() => {
28
+ if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
29
+ }, [messages, isLoading]);
30
+
31
+ const handleSubmit = async (e?: React.FormEvent) => {
32
+ if (e) e.preventDefault();
33
+ if (!input.trim() || isLoading) return;
15
34
 
16
- const handleSubmit = (e: React.FormEvent) => {
17
- e.preventDefault();
18
- sendMessage(input);
35
+ const userText = input;
36
+ setMessages(prev => [...prev, { id: `u-${Date.now()}`, author: 'You', content: userText, isUser: true }]);
19
37
  setInput('');
38
+ setIsLoading(true);
39
+
40
+ try {
41
+ const apiKey = localStorage.getItem('ramme_vault_gemini_key');
42
+ if (!apiKey) {
43
+ setMessages(prev => [...prev, {
44
+ id: `err-${Date.now()}`,
45
+ author: 'System',
46
+ content: 'No API key found. Please configure your Gemini API key in Settings → Theme Engine.',
47
+ isUser: false
48
+ }]);
49
+ setIsLoading(false);
50
+ return;
51
+ }
52
+
53
+ const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`, {
54
+ method: 'POST',
55
+ headers: { 'Content-Type': 'application/json' },
56
+ body: JSON.stringify({
57
+ contents: [
58
+ { role: "user", parts: [{ text: `INSTRUCTION: ${systemInstruction}` }] },
59
+ { role: "model", parts: [{ text: "Understood." }] },
60
+ ...apiHistory,
61
+ { role: "user", parts: [{ text: userText }] }
62
+ ]
63
+ })
64
+ });
65
+
66
+ const data = await response.json();
67
+ const aiText = data.candidates?.[0]?.content?.parts?.[0]?.text || "No response.";
68
+
69
+ setMessages(prev => [...prev, { id: `ai-${Date.now()}`, author: 'AI Agent', content: aiText, isUser: false }]);
70
+ setApiHistory([...apiHistory, { role: "user", parts: [{ text: userText }] }, { role: "model", parts: [{ text: aiText }] }]);
71
+ } catch (err: any) {
72
+ setMessages(prev => [...prev, { id: `err-${Date.now()}`, author: 'System', content: `Error: ${err.message}`, isUser: false }]);
73
+ } finally {
74
+ setIsLoading(false);
75
+ }
20
76
  };
21
77
 
22
78
  return (
23
- <div className="p-4 sm:p-6 lg:p-8 space-y-8 h-[calc(100vh-64px)] flex flex-col">
79
+ <div className="p-4 sm:p-6 lg:p-8 space-y-6 h-[calc(100vh-64px)] flex flex-col">
24
80
  <PageHeader
25
81
  title="AI Assistant"
26
82
  description="Full-screen command center for Ramme AI."
27
83
  />
28
84
 
29
- {/* We use flex-1 on the Card to make it fill the remaining screen space
30
- perfectly, creating that immersive "ChatGPT" feel.
31
- */}
32
- <Card className="flex-1 flex flex-col min-h-0 shadow-sm border-border/50">
33
- <div className="flex-1 overflow-hidden">
85
+ <Card className="flex-1 flex flex-col min-h-0 shadow-sm border-border/50 overflow-hidden">
86
+ <div className="px-4 py-2 border-b flex justify-between items-center bg-muted/20">
87
+ <div className="flex items-center gap-2">
88
+ <div className={`w-2 h-2 rounded-full ${isLoading ? 'bg-amber-500 animate-pulse' : 'bg-emerald-500'}`} />
89
+ <span className="font-bold text-[10px] uppercase tracking-widest opacity-60">Ramme Assistant</span>
90
+ </div>
91
+ </div>
92
+
93
+ <div ref={scrollRef} className="flex-1 overflow-y-auto px-4 py-4 custom-scrollbar">
34
94
  <Conversation>
35
95
  {messages.map((msg) => (
36
- <Message
37
- key={msg.id}
38
- author={msg.author}
39
- content={msg.content}
40
- isUser={msg.isUser}
41
- loading={msg.loading}
42
- suggestions={msg.suggestions}
43
- onSuggestionClick={(s) => sendMessage(s)}
44
- />
96
+ <div key={msg.id} className={`flex ${msg.isUser ? 'justify-end' : 'justify-start'} mb-3`}>
97
+ <div className="max-w-[98%] w-full">
98
+ <Message
99
+ author={msg.author}
100
+ isUser={msg.isUser}
101
+ // @ts-ignore
102
+ content={
103
+ <div className="prose prose-sm dark:prose-invert max-w-none text-foreground leading-relaxed">
104
+ <ReactMarkdown
105
+ remarkPlugins={[remarkGfm]}
106
+ components={{
107
+ p: ({children}) => <p className="mb-4 last:mb-0">{children}</p>,
108
+ ol: ({children}) => <ol className="list-decimal pl-8 mb-4 space-y-2">{children}</ol>,
109
+ ul: ({children}) => <ul className="list-disc pl-8 mb-4 space-y-2">{children}</ul>,
110
+ li: ({children}) => <li className="pl-1">{children}</li>,
111
+ code({ node, inline, className, children, ...props }: any) {
112
+ const match = /language-(\w+)/.exec(className || '');
113
+ return !inline && match ? (
114
+ <div className="my-5 rounded-lg overflow-hidden border border-border bg-[#0d0d0d] shadow-xl">
115
+ <div className="px-3 py-1 bg-white/5 border-b border-white/10 flex justify-between">
116
+ <span className="text-[9px] text-muted-foreground uppercase">{match[1]}</span>
117
+ </div>
118
+ <SyntaxHighlighter
119
+ style={oneDark}
120
+ language={match[1]}
121
+ PreTag="div"
122
+ customStyle={{ margin: 0, padding: '1.2rem', fontSize: '0.8rem', background: 'transparent' }}
123
+ {...props}
124
+ >
125
+ {String(children).replace(/\n$/, '')}
126
+ </SyntaxHighlighter>
127
+ </div>
128
+ ) : (
129
+ <code className="bg-muted px-1.5 py-0.5 rounded font-mono text-primary text-[0.9em]" {...props}>
130
+ {children}
131
+ </code>
132
+ );
133
+ },
134
+ }}
135
+ >
136
+ {msg.content}
137
+ </ReactMarkdown>
138
+ </div>
139
+ }
140
+ />
141
+ </div>
142
+ </div>
45
143
  ))}
46
- {isLoading && <Message author="AI Agent" isUser={false} loading />}
47
144
  </Conversation>
48
145
  </div>
49
-
50
- <div className="p-4 border-t bg-card">
51
- <PromptInput
52
- value={input}
53
- onChange={(e) => setInput(e.target.value)}
54
- onSubmit={handleSubmit}
55
- placeholder="Type a command or ask a question..."
56
- />
146
+
147
+ <div className="p-4 border-t bg-card/40 backdrop-blur-sm">
148
+ <PromptInput value={input} onChange={(e) => setInput(e.target.value)} onSubmit={handleSubmit} placeholder="Ask about Ramme..." />
57
149
  </div>
58
150
  </Card>
151
+
152
+ <style dangerouslySetInnerHTML={{ __html: `
153
+ .custom-scrollbar::-webkit-scrollbar { width: 4px; }
154
+ .custom-scrollbar::-webkit-scrollbar-track { background: transparent; }
155
+ .custom-scrollbar::-webkit-scrollbar-thumb { background: rgb(var(--app-primary-color), 0.2); border-radius: 10px; }
156
+ .custom-scrollbar::-webkit-scrollbar-thumb:hover { background: rgb(var(--app-primary-color), 0.5); }
157
+ `}} />
59
158
  </div>
60
159
  );
61
160
  };
@@ -7,6 +7,19 @@ const EMERGENCY_ADMIN: User = {
7
7
  id: 'usr_admin_force',
8
8
  name: 'Alex Admin (Recovery)',
9
9
  email: 'alex@example.com',
10
+ password: 'admin',
11
+ role: 'admin',
12
+ status: 'active',
13
+ joinedAt: new Date().toISOString(),
14
+ lastActive: new Date().toISOString()
15
+ };
16
+
17
+ // 🌐 DEFAULT GUEST: Auto-login for frictionless marketing site experience
18
+ const DEFAULT_GUEST: User = {
19
+ id: 'usr_guest',
20
+ name: 'Guest Creator',
21
+ email: 'guest@ramme.io',
22
+ password: 'guest',
10
23
  role: 'admin',
11
24
  status: 'active',
12
25
  joinedAt: new Date().toISOString(),
@@ -27,10 +40,10 @@ interface AuthContextType {
27
40
  const AuthContext = createContext<AuthContextType | undefined>(undefined);
28
41
 
29
42
  export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
30
- const [user, setUser] = useState<User | null>(null);
31
- const [isLoading, setIsLoading] = useState(true);
43
+ const [user, setUser] = useState<User | null>(DEFAULT_GUEST);
44
+ const [isLoading, setIsLoading] = useState(false);
32
45
 
33
- // 1. Mount: Check for existing session
46
+ // 1. Mount: Check for existing session, fall back to guest
34
47
  useEffect(() => {
35
48
  const storedUser = localStorage.getItem(SESSION_KEY);
36
49
  if (storedUser) {
@@ -38,15 +51,12 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
38
51
  const parsed = JSON.parse(storedUser);
39
52
  if (parsed && parsed.id) {
40
53
  setUser(parsed);
41
- } else {
42
- localStorage.removeItem(SESSION_KEY);
43
54
  }
44
55
  } catch (e) {
45
56
  console.error("Failed to parse session", e);
46
57
  localStorage.removeItem(SESSION_KEY);
47
58
  }
48
59
  }
49
- setIsLoading(false);
50
60
  }, []);
51
61
 
52
62
  // 2. Action: Login
@@ -14,8 +14,8 @@ export const AppConfigProvider = ({ children }: { children: ReactNode }) => {
14
14
 
15
15
  const [enableCRT, setEnableCRT] = useState(() => {
16
16
  const stored = localStorage.getItem('app_enable_crt');
17
- // Default to true for that retro-first experience
18
- return stored !== null ? JSON.parse(stored) : true;
17
+ // Default to false for clean marketing site clarity
18
+ return stored !== null ? JSON.parse(stored) : false;
19
19
  });
20
20
 
21
21
  useEffect(() => {
@@ -0,0 +1,149 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import { useDevice } from '@ramme-io/kernel';
3
+ import { CircularDial, WaterPumpControl, TelemetryLog } from '@ramme-io/ui';
4
+ import type { TelemetryLogRow } from '@ramme-io/ui';
5
+ import type { PumpMode } from '@ramme-io/ui';
6
+ import { StandardPageLayout } from '../../../components/layout/StandardPageLayout';
7
+
8
+ const EdgeTelemetryDemo: React.FC = () => {
9
+ const telemetry = useDevice();
10
+ const [fps, setFps] = useState(0);
11
+ const [pumpMode, setPumpMode] = useState<PumpMode>('AUTO');
12
+ const [targetMoisture, setTargetMoisture] = useState(40);
13
+ const [logData, setLogData] = useState<TelemetryLogRow[]>([]);
14
+ const logBuffer = useRef<TelemetryLogRow[]>([]);
15
+ const frameCount = useRef(0);
16
+ const rafId = useRef(0);
17
+
18
+ // Append incoming telemetry to the historical log buffer (capped at 100)
19
+ useEffect(() => {
20
+ if (!telemetry) return;
21
+ const row: TelemetryLogRow = {
22
+ timestamp: Date.now(),
23
+ soilMoisture: telemetry.soilMoisture,
24
+ ambientLight: telemetry.ambientLight,
25
+ temperature: telemetry.temperature,
26
+ };
27
+ logBuffer.current = [...logBuffer.current.slice(-99), row];
28
+ setLogData(logBuffer.current);
29
+ }, [telemetry]);
30
+
31
+ // Continuous RAF loop tallies frames; 1s interval reads & resets the tally
32
+ useEffect(() => {
33
+ const tick = () => {
34
+ frameCount.current += 1;
35
+ rafId.current = requestAnimationFrame(tick);
36
+ };
37
+ rafId.current = requestAnimationFrame(tick);
38
+
39
+ const interval = setInterval(() => {
40
+ setFps(frameCount.current);
41
+ frameCount.current = 0;
42
+ }, 1000);
43
+
44
+ return () => {
45
+ cancelAnimationFrame(rafId.current);
46
+ clearInterval(interval);
47
+ };
48
+ }, []);
49
+
50
+ return (
51
+ <StandardPageLayout
52
+ title="Edge Telemetry Engine"
53
+ description="Hardware-in-the-loop simulation testing 50Hz MQTT ingestion and Zero Jank UI rendering."
54
+ >
55
+ {/* Status Bar */}
56
+ <div className="flex items-center gap-4 p-4 rounded-2xl bg-card border border-border font-mono text-xs mb-8">
57
+ <div className="flex items-center gap-2">
58
+ <div className="h-2 w-2 rounded-full bg-green-500 animate-pulse" />
59
+ <span className="font-bold text-foreground uppercase tracking-widest">Kernel Link Active</span>
60
+ </div>
61
+ <div className="ml-auto flex items-center gap-4">
62
+ <span className={fps > 20 ? "text-red-500 font-bold" : "text-green-500 font-bold"}>
63
+ {fps} FPS
64
+ </span>
65
+ <span className="text-muted-foreground">
66
+ Target: ~15 FPS | Simulator: 50Hz
67
+ </span>
68
+ </div>
69
+ </div>
70
+
71
+ {/* Telemetry Grid */}
72
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
73
+ {/* Soil Moisture Dial */}
74
+ <div className="flex flex-col items-center gap-4 p-8 rounded-3xl bg-card border border-border">
75
+ {telemetry ? (
76
+ <CircularDial
77
+ value={telemetry.soilMoisture ?? 0}
78
+ label="Soil Moisture"
79
+ unit="%"
80
+ size={180}
81
+ />
82
+ ) : (
83
+ <div className="text-muted-foreground text-sm">Awaiting telemetry...</div>
84
+ )}
85
+ </div>
86
+
87
+ {/* Ambient Light Dial */}
88
+ <div className="flex flex-col items-center gap-4 p-8 rounded-3xl bg-card border border-border">
89
+ {telemetry ? (
90
+ <CircularDial
91
+ value={telemetry.ambientLight ?? 0}
92
+ min={0}
93
+ max={100000}
94
+ label="Ambient Light"
95
+ unit=" lx"
96
+ size={180}
97
+ />
98
+ ) : (
99
+ <div className="text-muted-foreground text-sm">Awaiting telemetry...</div>
100
+ )}
101
+ </div>
102
+
103
+ {/* Temperature Dial */}
104
+ <div className="flex flex-col items-center gap-4 p-8 rounded-3xl bg-card border border-border">
105
+ {telemetry ? (
106
+ <CircularDial
107
+ value={telemetry.temperature ?? 0}
108
+ min={-40}
109
+ max={125}
110
+ label="Temperature"
111
+ unit="°C"
112
+ size={180}
113
+ />
114
+ ) : (
115
+ <div className="text-muted-foreground text-sm">Awaiting telemetry...</div>
116
+ )}
117
+ </div>
118
+ </div>
119
+
120
+ {/* Command: Water Pump Actuation */}
121
+ <div className="mt-8">
122
+ <WaterPumpControl
123
+ pumpMode={pumpMode}
124
+ targetMoisture={targetMoisture}
125
+ onCommand={(payload) => {
126
+ setPumpMode(payload.pumpMode);
127
+ setTargetMoisture(payload.targetMoisture);
128
+ console.log('[MQTT PUBLISH MOCK]', payload);
129
+ }}
130
+ className="max-w-md"
131
+ />
132
+ </div>
133
+
134
+ {/* Raw Payload Debug */}
135
+ <div className="mt-8 p-6 rounded-2xl bg-card border border-border">
136
+ <div className="font-mono text-[10px] uppercase tracking-[0.3em] text-muted-foreground mb-3 font-bold">Raw Smoothed Payload</div>
137
+ <pre className="font-mono text-sm text-foreground/80 leading-relaxed tabular-nums">
138
+ {JSON.stringify(telemetry, null, 2)}
139
+ </pre>
140
+ </div>
141
+ {/* Telemetry Log — AG Grid virtualized scroll */}
142
+ <div className="mt-8">
143
+ <TelemetryLog rowData={logData} maxRows={100} height="320px" />
144
+ </div>
145
+ </StandardPageLayout>
146
+ );
147
+ };
148
+
149
+ export default EdgeTelemetryDemo;
@@ -18,7 +18,7 @@ const AboutRamme: React.FC = () => {
18
18
 
19
19
  {/* --- 01: THE MANIFESTO --- */}
20
20
  <header className="space-y-8 pt-20 border-l-4 pl-12" style={{ borderColor: 'rgb(var(--app-primary-color))' }}>
21
- <Badge variant="primary" className="uppercase tracking-[0.3em] text-[10px] px-4 py-1.5 border-primary/20 bg-primary/5">
21
+ <Badge variant="primary" className="uppercase tracking-[0.3em] text-[10px] px-4 py-1.5 border-primary/40 bg-primary/10 text-primary">
22
22
  Core Philosophy
23
23
  </Badge>
24
24
  <h1 className="text-7xl md:text-[110px] font-black tracking-[-0.02em] leading-[0.8] text-foreground uppercase">
@@ -40,19 +40,19 @@ const AboutRamme: React.FC = () => {
40
40
  <p className="text-muted-foreground leading-relaxed text-lg">
41
41
  Traditional prototypes are fragile. They break when a user clicks outside a hotspot or refreshes the page. Ramme uses a <span style={{ color: 'rgb(var(--app-info-color))' }} className="font-bold underline decoration-info/20">Local-First Kernel</span> that simulates a real database, real authentication, and real logic.
42
42
  </p>
43
- <div className="p-8 rounded-3xl bg-muted/5 border border-white/5 italic text-sm text-foreground/60 leading-relaxed shadow-inner">
43
+ <div className="p-8 rounded-3xl bg-muted/5 border border-border italic text-sm text-foreground/60 leading-relaxed shadow-inner">
44
44
  "We didn't just want a UI kit. We wanted an OS for ideas—a environment where the data lives and breathes alongside the design."
45
45
  </div>
46
46
  </div>
47
47
  <div className="grid grid-cols-2 gap-6">
48
- <Card className="p-8 bg-black/40 border-white/5 flex flex-col gap-5 rounded-[2rem] hover:translate-y-[-4px] transition-transform">
48
+ <Card className="p-8 bg-card/60 border-border flex flex-col gap-5 rounded-[2rem] hover:translate-y-[-4px] transition-transform">
49
49
  <Icon name="database" style={{ color: 'rgb(var(--app-success-color))' }} size={28} />
50
50
  <div className="space-y-2">
51
51
  <div className="text-[10px] font-black uppercase tracking-widest text-success/60">Local Engine</div>
52
52
  <p className="text-xs text-muted-foreground font-medium leading-relaxed">Auto-persisted JSON state that lives in the browser's kernel.</p>
53
53
  </div>
54
54
  </Card>
55
- <Card className="p-8 bg-black/40 border-white/5 flex flex-col gap-5 rounded-[2rem] hover:translate-y-[-4px] transition-transform">
55
+ <Card className="p-8 bg-card/60 border-border flex flex-col gap-5 rounded-[2rem] hover:translate-y-[-4px] transition-transform">
56
56
  <Icon name="shield-check" style={{ color: 'rgb(var(--app-primary-color))' }} size={28} />
57
57
  <div className="space-y-2">
58
58
  <div className="text-[10px] font-black uppercase tracking-widest text-primary/60">Auth Simulation</div>
@@ -68,12 +68,12 @@ const AboutRamme: React.FC = () => {
68
68
  <div className="h-2 w-2 rounded-full shadow-[0_0_10px_rgba(var(--app-info-color),0.5)]" style={{ backgroundColor: 'rgb(var(--app-info-color))' }} />
69
69
  <h3 className="text-[10px] font-black uppercase tracking-widest opacity-40 text-foreground">Entry Point</h3>
70
70
  </div>
71
- <Card className="p-0 overflow-hidden rounded-3xl border-white/5 shadow-2xl">
71
+ <Card className="p-0 overflow-hidden rounded-3xl border-border shadow-2xl">
72
72
  <RammeCodeBlock
73
73
  variant="info"
74
74
  title="Interactive Terminal"
75
75
  language="bash"
76
- code="npx @ramme-io/create-app@latest my-prototype"
76
+ code="npm create @ramme-io/app my-prototype"
77
77
  />
78
78
  </Card>
79
79
  </section>
@@ -127,7 +127,7 @@ set({ lastLogin: new Date() });`}
127
127
  { label: 'Tailwind CSS', icon: 'wind', color: 'text-sky-400' }
128
128
  ].map((tech) => (
129
129
  <div key={tech.label} className="flex flex-col items-center gap-4 group cursor-default">
130
- <div className="p-5 rounded-2xl bg-white/5 border border-white/5 group-hover:border-white/10 transition-all">
130
+ <div className="p-5 rounded-2xl bg-muted/10 border border-border group-hover:border-border/70 transition-all">
131
131
  <Icon name={tech.icon as any} className={tech.color} size={32} />
132
132
  </div>
133
133
  <span className="font-bold tracking-tight text-sm opacity-50 group-hover:opacity-100 transition-opacity">{tech.label}</span>
@@ -137,18 +137,18 @@ set({ lastLogin: new Date() });`}
137
137
  </section>
138
138
 
139
139
  {/* --- FINAL CTA --- */}
140
- <footer className="text-center space-y-12 bg-black/40 border border-white/5 p-20 rounded-[4rem] shadow-2xl relative overflow-hidden group">
140
+ <footer className="text-center space-y-12 bg-card/60 border border-border p-20 rounded-[4rem] shadow-2xl relative overflow-hidden group">
141
141
  <div className="relative z-10 space-y-8">
142
142
  <h2 className="text-6xl font-black tracking-tight text-foreground uppercase leading-[0.9]">
143
143
  Ready to bypass the <br/>
144
144
  <span style={{ color: 'rgb(var(--app-primary-color))' }}>boilerplate?</span>
145
145
  </h2>
146
146
  <div className="flex flex-col sm:flex-row justify-center gap-6 pt-4">
147
- <Button size="lg" className="px-12 h-20 text-xl font-black rounded-full shadow-2xl hover:scale-105 transition-transform" onClick={() => navigate('/dashboard')}>
148
- Initialize Kernel
147
+ <Button size="lg" className="px-12 h-20 text-xl font-black rounded-full shadow-2xl hover:scale-105 transition-transform" onClick={() => navigate('/docs/styleguide/templates')}>
148
+ Browse the Style Guide
149
149
  </Button>
150
- <Button size="lg" variant="outline" className="px-12 h-20 text-xl font-black rounded-full border-white/10 hover:bg-white/5" onClick={() => navigate('/tutorial')}>
151
- View Tutorial
150
+ <Button size="lg" variant="outline" className="px-12 h-20 text-xl font-black rounded-full border-border hover:bg-muted/10" onClick={() => navigate('/dashboard/tutorial')}>
151
+ Start Tutorial
152
152
  </Button>
153
153
  </div>
154
154
  </div>