@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
@@ -0,0 +1,36 @@
1
+ import { useEffect, useCallback, useRef } from 'react';
2
+ import { useAppConfig } from '../features/config/AppConfigContext';
3
+
4
+ /**
5
+ * Global keyboard shortcuts for the Ramme Studio.
6
+ * Ctrl+Shift+C — Toggle CRT/LCD scanline effect with subtle flash feedback.
7
+ */
8
+ export function useStudioHotkeys() {
9
+ const { enableCRT, setEnableCRT } = useAppConfig();
10
+ const enableCRTRef = useRef(enableCRT);
11
+ enableCRTRef.current = enableCRT;
12
+
13
+ const handleKeyDown = useCallback((e: KeyboardEvent) => {
14
+ // Only respond to Ctrl+Shift+C
15
+ if (!e.ctrlKey || !e.shiftKey || e.key !== 'C') return;
16
+
17
+ // Don't fire when user is typing in form fields
18
+ const tag = (e.target as HTMLElement)?.tagName;
19
+ if (tag === 'INPUT' || tag === 'TEXTAREA') return;
20
+ if ((e.target as HTMLElement)?.isContentEditable) return;
21
+
22
+ e.preventDefault();
23
+ setEnableCRT(!enableCRTRef.current);
24
+
25
+ // Subtle flash feedback
26
+ document.body.classList.remove('studio-flash');
27
+ void document.body.offsetWidth; // force reflow to restart animation
28
+ document.body.classList.add('studio-flash');
29
+ setTimeout(() => document.body.classList.remove('studio-flash'), 250);
30
+ }, [setEnableCRT]);
31
+
32
+ useEffect(() => {
33
+ window.addEventListener('keydown', handleKeyDown);
34
+ return () => window.removeEventListener('keydown', handleKeyDown);
35
+ }, [handleKeyDown]);
36
+ }
@@ -3,11 +3,92 @@
3
3
  @tailwind utilities;
4
4
 
5
5
 
6
+ /* Smooth theme morphing — transitions all CSS variable-driven colors */
7
+ html {
8
+ transition: background-color 0.5s ease, color 0.5s ease;
9
+ }
10
+ html *,
11
+ html *::before,
12
+ html *::after {
13
+ transition: background-color 0.5s ease, color 0.3s ease, border-color 0.4s ease, box-shadow 0.4s ease;
14
+ }
15
+ /* AG Grid needs instant hover/selection feedback — exempt from theme morphing */
16
+ .ag-theme-quartz *,
17
+ .ag-theme-quartz *::before,
18
+ .ag-theme-quartz *::after {
19
+ transition: none !important;
20
+ }
21
+
6
22
  /* Add this new rule */
7
23
  .sidebar-panel {
8
24
  min-width: 220px !important;
9
25
  }
10
26
 
27
+ /* ===================================================================
28
+ AG GRID THEME BRIDGE FOR RAMME
29
+ Uses --app-* CSS variables which are set by:
30
+ - Standard themes: CSS classes (.dark, .midnight, etc.) in UI index.css
31
+ - AI/Custom themes: inline styles on <html> via ThemeContext
32
+ =================================================================== */
33
+
34
+ /* --- AG Grid element overrides --- */
35
+ /* Uses ONLY base --app-* vars that the AI theme generator sets as inline styles on <html> */
36
+ .ag-theme-quartz.ag-theme-ramme .ag-root-wrapper {
37
+ background-color: rgb(var(--app-card-bg-color)) !important;
38
+ color: rgb(var(--app-text-color)) !important;
39
+ border-color: rgb(var(--app-border-color)) !important;
40
+ }
41
+ .ag-theme-quartz.ag-theme-ramme .ag-header,
42
+ .ag-theme-quartz.ag-theme-ramme .ag-header-row {
43
+ background-color: rgb(var(--app-muted-bg)) !important;
44
+ color: rgb(var(--app-text-color)) !important;
45
+ }
46
+ .ag-theme-quartz.ag-theme-ramme .ag-header-cell {
47
+ background-color: transparent !important;
48
+ }
49
+ .ag-theme-quartz.ag-theme-ramme .ag-header-cell-label {
50
+ font-weight: 600;
51
+ }
52
+ .ag-theme-quartz.ag-theme-ramme .ag-row,
53
+ .ag-theme-quartz.ag-theme-ramme .ag-row-odd,
54
+ .ag-theme-quartz.ag-theme-ramme .ag-row-even,
55
+ .ag-theme-quartz.ag-theme-ramme .ag-row-no-focus {
56
+ background-color: rgb(var(--app-card-bg-color)) !important;
57
+ color: rgb(var(--app-text-color)) !important;
58
+ border-bottom-color: rgb(var(--app-border-color)) !important;
59
+ }
60
+ .ag-theme-quartz.ag-theme-ramme .ag-row:hover,
61
+ .ag-theme-quartz.ag-theme-ramme .ag-row-hover,
62
+ .ag-theme-quartz.ag-theme-ramme .ag-row.ag-row-hover {
63
+ background-color: rgb(var(--app-bg-color)) !important;
64
+ }
65
+ .ag-theme-quartz.ag-theme-ramme .ag-row-hover .ag-cell,
66
+ .ag-theme-quartz.ag-theme-ramme .ag-row:hover .ag-cell {
67
+ background-color: rgb(var(--app-bg-color)) !important;
68
+ }
69
+ .ag-theme-quartz.ag-theme-ramme .ag-row-selected {
70
+ background-color: rgba(var(--app-primary-color), 0.15) !important;
71
+ }
72
+ .ag-theme-quartz.ag-theme-ramme .ag-body-viewport {
73
+ background-color: rgb(var(--app-card-bg-color)) !important;
74
+ }
75
+ .ag-theme-quartz.ag-theme-ramme .ag-floating-filter {
76
+ background-color: rgb(var(--app-card-bg-color)) !important;
77
+ }
78
+ .ag-theme-quartz.ag-theme-ramme .ag-floating-filter-input {
79
+ background-color: rgb(var(--app-input-bg-color)) !important;
80
+ color: rgb(var(--app-text-color)) !important;
81
+ }
82
+ .ag-theme-quartz.ag-theme-ramme .ag-paging-panel {
83
+ background-color: rgb(var(--app-card-bg-color)) !important;
84
+ color: rgb(var(--app-muted-text)) !important;
85
+ border-top: 1px solid rgb(var(--app-border-color)) !important;
86
+ }
87
+ .ag-theme-quartz.ag-theme-ramme .ag-cell {
88
+ display: flex;
89
+ align-items: center;
90
+ }
91
+
11
92
 
12
93
  /*
13
94
  =================================================================
@@ -68,4 +149,13 @@ body.crt-mode::after {
68
149
  @keyframes crtAnimation {
69
150
  0% { background-position: 0 0; }
70
151
  100% { background-position: 0 10000%; }
71
- }
152
+ }
153
+
154
+ /* Studio Hotkey flash feedback (Ctrl+Shift+C) */
155
+ @keyframes studio-flash {
156
+ 0% { box-shadow: inset 0 0 0 3px rgba(255, 255, 255, 0.6); }
157
+ 100% { box-shadow: inset 0 0 0 3px rgba(255, 255, 255, 0); }
158
+ }
159
+ .studio-flash {
160
+ animation: studio-flash 200ms ease-out;
161
+ }
@@ -12,6 +12,8 @@ import App from './App.tsx';
12
12
 
13
13
  import { applyThemeToDom, loadGoogleFont } from './features/theme/utils/themeUtils.ts'; // adjust path as needed
14
14
 
15
+ import 'ag-grid-community/styles/ag-grid.css';
16
+ import 'ag-grid-community/styles/ag-theme-quartz.css';
15
17
  import "@ramme-io/ui/index.css";
16
18
  import './index.css';
17
19
  import 'ag-grid-enterprise';
@@ -23,7 +25,7 @@ const CURRENT_SCHEMA_VERSION = 'v2-added-passwords';
23
25
 
24
26
  // --- THEME HYDRATION LOGIC ---
25
27
  const ThemeHydrator = () => {
26
- React.useEffect(() => {
28
+ const applyPersistedTheme = React.useCallback(() => {
27
29
  const savedTheme = localStorage.getItem('ramme_persisted_theme');
28
30
  if (savedTheme) {
29
31
  try {
@@ -40,7 +42,47 @@ const ThemeHydrator = () => {
40
42
  }
41
43
  }, []);
42
44
 
43
- return null; // This component doesn't render anything
45
+ React.useEffect(() => {
46
+ // Apply immediately on mount
47
+ applyPersistedTheme();
48
+
49
+ // Re-apply on any navigation or visibility change
50
+ const handleVisibilityChange = () => {
51
+ if (document.visibilityState === 'visible') {
52
+ applyPersistedTheme();
53
+ }
54
+ };
55
+
56
+ // Watch for DOM mutations that might reset the theme
57
+ const observer = new MutationObserver(() => {
58
+ const savedTheme = localStorage.getItem('ramme_persisted_theme');
59
+ if (savedTheme) {
60
+ const theme = JSON.parse(savedTheme);
61
+ // Check if theme has been reset by comparing a key variable
62
+ const currentPrimary = getComputedStyle(document.documentElement)
63
+ .getPropertyValue('--app-primary-color').trim();
64
+ const expectedPrimary = theme.cssVars['--app-primary-color'];
65
+
66
+ if (currentPrimary && expectedPrimary && currentPrimary !== expectedPrimary) {
67
+ applyPersistedTheme();
68
+ }
69
+ }
70
+ });
71
+
72
+ observer.observe(document.documentElement, {
73
+ attributes: true,
74
+ attributeFilter: ['class', 'style']
75
+ });
76
+
77
+ document.addEventListener('visibilitychange', handleVisibilityChange);
78
+
79
+ return () => {
80
+ observer.disconnect();
81
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
82
+ };
83
+ }, [applyPersistedTheme]);
84
+
85
+ return null;
44
86
  };
45
87
 
46
88
  const runMigration = () => {
@@ -83,6 +83,11 @@ const DashboardLayout: React.FC = () => {
83
83
  label="Documentation"
84
84
  onClick={() => navigate('/docs')}
85
85
  />
86
+ <ButtonItem
87
+ icon="cpu"
88
+ label="Bodewell"
89
+ onClick={() => navigate('/bodewell/fleet')}
90
+ />
86
91
  <ButtonItem
87
92
  icon="refresh-cw"
88
93
  label="Clear Cache & Reset"
@@ -94,7 +99,7 @@ const DashboardLayout: React.FC = () => {
94
99
 
95
100
 
96
101
 
97
- <main className="flex-1 overflow-y-auto bg-muted/20 relative w-full">
102
+ <main id="main-scroll-container" className="flex-1 overflow-y-auto bg-muted/20 relative w-full">
98
103
  <Outlet />
99
104
  </main>
100
105
 
@@ -10,7 +10,7 @@ import RammeTutorial from '../../features/onboarding/pages/RammeTutorial';
10
10
  import AiChat from '../../features/ai/pages/AiChat';
11
11
 
12
12
  // ✅ NEW: Import from the Feature Domain (Clean API)
13
- import { UsersPage } from '../../features/users';
13
+ import { UsersPage } from '../../features/users';
14
14
 
15
15
  export const dashboardSitemap: SitemapEntry[] = [
16
16
  // 1. The New Landing Page
@@ -6,6 +6,7 @@ import { OverviewPage } from '../../features/overview';
6
6
  import AiChat from '../../features/ai/pages/AiChat';
7
7
 
8
8
  import Styleguide from '../../features/styleguide/Styleguide';
9
+ import EdgeTelemetryDemo from '../../features/docs/pages/EdgeTelemetryDemo';
9
10
 
10
11
  // --- SECTION IMPORTS ---
11
12
  import TemplatesSection from '../../features/styleguide/sections/templates/TemplatesSection';
@@ -42,6 +43,13 @@ export const docsSitemap: ExtendedSitemapEntry[] = [
42
43
  icon: 'bot',
43
44
  component: AiChat,
44
45
  },
46
+ {
47
+ id: 'edge-telemetry',
48
+ path: 'edge-telemetry',
49
+ title: 'Edge Telemetry',
50
+ icon: 'activity',
51
+ component: EdgeTelemetryDemo,
52
+ },
45
53
  {
46
54
  id: 'styleguide',
47
55
  path: 'styleguide',
@@ -4,7 +4,6 @@ import {
4
4
  Sidebar,
5
5
  type SidebarItem,
6
6
  type IconName,
7
- // 🟢 ADD: ButtonItem
8
7
  ButtonItem
9
8
  } from '@ramme-io/ui';
10
9
  import { settingsSitemap } from './settings.sitemap';
@@ -13,7 +12,7 @@ import { useAuth } from '../../features/auth/AuthContext';
13
12
  import { SitemapProvider } from '@ramme-io/kernel';
14
13
  import PageTitleUpdater from '../../components/PageTitleUpdater';
15
14
  import TemplateSwitcher from '../../components/TemplateSwitcher';
16
- import { useAppConfig } from '../../features/config/AppConfigContext'; // 🟢 Import
15
+ import { useAppConfig } from '../../features/config/AppConfigContext';
17
16
 
18
17
  const SettingsLayout: React.FC = () => {
19
18
  const navigate = useNavigate();
@@ -31,32 +30,15 @@ const SettingsLayout: React.FC = () => {
31
30
 
32
31
  // --- Data Mapping ---
33
32
  const sidebarItems: SidebarItem[] = useMemo(() => {
34
- // 1. Navigation Items
35
- const navItems: SidebarItem[] = settingsSitemap.map((item) => ({
33
+ // We now only map the actual navigation items from the sitemap.
34
+ // Logout and Reset have been moved to the User Menu below.
35
+ return settingsSitemap.map((item) => ({
36
36
  id: item.id,
37
37
  label: item.title,
38
38
  icon: (item.icon as IconName) || 'settings',
39
39
  href: item.path ? `/settings/${item.path}` : '/settings',
40
40
  }));
41
-
42
- // 2. System Actions (Kept in sidebar list for visibility in Settings context)
43
- const actionItems: SidebarItem[] = [
44
- {
45
- id: 'action-reset',
46
- label: 'Reset Data',
47
- icon: 'refresh-cw' as IconName,
48
- onClick: handleResetData,
49
- },
50
- {
51
- id: 'action-logout',
52
- label: 'Logout',
53
- icon: 'log-out' as IconName,
54
- onClick: logout,
55
- }
56
- ];
57
-
58
- return [...navItems, ...actionItems];
59
- }, [logout]);
41
+ }, []);
60
42
 
61
43
  const activeItemId = useMemo(() => {
62
44
  const active = sidebarItems.find(item =>
@@ -84,9 +66,9 @@ const SettingsLayout: React.FC = () => {
84
66
  email: user.email,
85
67
  avatarUrl: undefined
86
68
  } : undefined}
87
- // ✅ ADD: Logout Handler for the User Badge
69
+ // ✅ Handles the logout action from the profile menu
88
70
  onLogout={logout}
89
- // ✅ ADD: User Menu Items (Documents & Reset)
71
+ // ✅ Keeps the secondary actions tucked away in the user menu
90
72
  userMenuChildren={
91
73
  <>
92
74
  <ButtonItem
@@ -94,6 +76,11 @@ const SettingsLayout: React.FC = () => {
94
76
  label="Documents"
95
77
  onClick={() => navigate('/docs')}
96
78
  />
79
+ <ButtonItem
80
+ icon="cpu"
81
+ label="Bodewell"
82
+ onClick={() => navigate('/bodewell/fleet')}
83
+ />
97
84
  <ButtonItem
98
85
  icon="refresh-cw"
99
86
  label="Reset Data"
@@ -119,7 +106,7 @@ const SettingsLayout: React.FC = () => {
119
106
  />
120
107
 
121
108
  <div className="flex flex-col flex-1 overflow-hidden">
122
- <main className="flex-1 overflow-y-auto p-8 bg-muted/20">
109
+ <main id="main-scroll-container" className="flex-1 overflow-y-auto p-8 bg-muted/20">
123
110
  <Outlet />
124
111
  </main>
125
112
  </div>
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';
@@ -16,6 +16,7 @@
16
16
  "moduleDetection": "force",
17
17
  "noEmit": true,
18
18
  "jsx": "react-jsx",
19
+ "types": ["vitest/globals"],
19
20
 
20
21
  /* Linting */
21
22
  "strict": true,
@@ -1,3 +1,4 @@
1
+ /// <reference types="vitest" />
1
2
  import { defineConfig } from 'vite';
2
3
  import react from '@vitejs/plugin-react';
3
4
  import path from 'path';
@@ -78,6 +79,12 @@ export default defineConfig(() => {
78
79
  alias: aliases,
79
80
  },
80
81
 
82
+ test: {
83
+ globals: true,
84
+ environment: 'jsdom',
85
+ setupFiles: './src/test/setup.ts',
86
+ },
87
+
81
88
  optimizeDeps: {
82
89
  include: [
83
90
  'recharts',