@techninja/clearstack 0.2.0

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 (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +81 -0
  3. package/bin/cli.js +62 -0
  4. package/docs/BACKEND_API_SPEC.md +281 -0
  5. package/docs/BUILD_LOG.md +193 -0
  6. package/docs/COMPONENT_PATTERNS.md +481 -0
  7. package/docs/CONVENTIONS.md +226 -0
  8. package/docs/FRONTEND_IMPLEMENTATION_RULES.md +239 -0
  9. package/docs/JSDOC_TYPING.md +86 -0
  10. package/docs/QUICKSTART.md +190 -0
  11. package/docs/SERVER_AND_DEPS.md +163 -0
  12. package/docs/STATE_AND_ROUTING.md +363 -0
  13. package/docs/TESTING.md +268 -0
  14. package/docs/app-spec/ENTITIES.md +37 -0
  15. package/docs/app-spec/README.md +19 -0
  16. package/lib/check.js +115 -0
  17. package/lib/copy.js +43 -0
  18. package/lib/init.js +73 -0
  19. package/lib/package-gen.js +83 -0
  20. package/lib/update.js +73 -0
  21. package/package.json +69 -0
  22. package/templates/fullstack/data/seed.json +1 -0
  23. package/templates/fullstack/src/api/db.js +75 -0
  24. package/templates/fullstack/src/api/entities.js +114 -0
  25. package/templates/fullstack/src/api/events.js +35 -0
  26. package/templates/fullstack/src/api/schemas.js +104 -0
  27. package/templates/fullstack/src/api/validate.js +52 -0
  28. package/templates/fullstack/src/pages/home/home-view.js +19 -0
  29. package/templates/fullstack/src/router/index.js +16 -0
  30. package/templates/fullstack/src/server.js +46 -0
  31. package/templates/fullstack/src/store/AppState.js +33 -0
  32. package/templates/fullstack/src/store/UserPrefs.js +31 -0
  33. package/templates/fullstack/src/store/realtimeSync.js +54 -0
  34. package/templates/shared/.configs/.prettierrc +8 -0
  35. package/templates/shared/.configs/eslint.config.js +64 -0
  36. package/templates/shared/.configs/jsconfig.json +24 -0
  37. package/templates/shared/.configs/web-test-runner.config.js +8 -0
  38. package/templates/shared/.env +9 -0
  39. package/templates/shared/.github/ISSUE_TEMPLATE/bug_report.md +42 -0
  40. package/templates/shared/.github/ISSUE_TEMPLATE/feature_request.md +30 -0
  41. package/templates/shared/.github/ISSUE_TEMPLATE/spec_correction.md +26 -0
  42. package/templates/shared/.github/pull_request_template.md +51 -0
  43. package/templates/shared/.github/workflows/spec.yml +46 -0
  44. package/templates/shared/README.md +22 -0
  45. package/templates/shared/docs/app-spec/README.md +40 -0
  46. package/templates/shared/docs/clearstack/BACKEND_API_SPEC.md +281 -0
  47. package/templates/shared/docs/clearstack/BUILD_LOG.md +193 -0
  48. package/templates/shared/docs/clearstack/COMPONENT_PATTERNS.md +481 -0
  49. package/templates/shared/docs/clearstack/CONVENTIONS.md +226 -0
  50. package/templates/shared/docs/clearstack/FRONTEND_IMPLEMENTATION_RULES.md +239 -0
  51. package/templates/shared/docs/clearstack/JSDOC_TYPING.md +86 -0
  52. package/templates/shared/docs/clearstack/QUICKSTART.md +190 -0
  53. package/templates/shared/docs/clearstack/SERVER_AND_DEPS.md +163 -0
  54. package/templates/shared/docs/clearstack/STATE_AND_ROUTING.md +363 -0
  55. package/templates/shared/docs/clearstack/TESTING.md +268 -0
  56. package/templates/shared/public/index.html +26 -0
  57. package/templates/shared/scripts/build-icons.js +86 -0
  58. package/templates/shared/scripts/vendor-deps.js +25 -0
  59. package/templates/shared/src/components/atoms/app-badge/app-badge.css +4 -0
  60. package/templates/shared/src/components/atoms/app-badge/app-badge.js +23 -0
  61. package/templates/shared/src/components/atoms/app-badge/app-badge.test.js +26 -0
  62. package/templates/shared/src/components/atoms/app-badge/index.js +1 -0
  63. package/templates/shared/src/components/atoms/app-button/app-button.css +3 -0
  64. package/templates/shared/src/components/atoms/app-button/app-button.js +41 -0
  65. package/templates/shared/src/components/atoms/app-button/app-button.test.js +43 -0
  66. package/templates/shared/src/components/atoms/app-button/index.js +1 -0
  67. package/templates/shared/src/components/atoms/app-icon/app-icon.css +4 -0
  68. package/templates/shared/src/components/atoms/app-icon/app-icon.js +57 -0
  69. package/templates/shared/src/components/atoms/app-icon/app-icon.test.js +30 -0
  70. package/templates/shared/src/components/atoms/app-icon/index.js +1 -0
  71. package/templates/shared/src/components/atoms/theme-toggle/index.js +1 -0
  72. package/templates/shared/src/components/atoms/theme-toggle/theme-toggle.css +10 -0
  73. package/templates/shared/src/components/atoms/theme-toggle/theme-toggle.js +42 -0
  74. package/templates/shared/src/styles/buttons.css +79 -0
  75. package/templates/shared/src/styles/components.css +31 -0
  76. package/templates/shared/src/styles/forms.css +20 -0
  77. package/templates/shared/src/styles/reset.css +32 -0
  78. package/templates/shared/src/styles/shared.css +135 -0
  79. package/templates/shared/src/styles/tokens.css +65 -0
  80. package/templates/shared/src/utils/formatDate.js +41 -0
  81. package/templates/shared/src/utils/statusColors.js +60 -0
  82. package/templates/static/src/pages/home/home-view.js +38 -0
  83. package/templates/static/src/router/index.js +16 -0
  84. package/templates/static/src/store/AppState.js +26 -0
@@ -0,0 +1,135 @@
1
+ /* Shared styles — error states, loading, icons, utilities, transitions.
2
+ Loaded globally in index.html. All light DOM components inherit these. */
3
+
4
+ /* --- Error & Validation States --- */
5
+
6
+ .error {
7
+ color: var(--color-danger);
8
+
9
+ & input,
10
+ & select,
11
+ & textarea {
12
+ border-color: var(--color-danger);
13
+ }
14
+ }
15
+
16
+ .error-message {
17
+ font-size: var(--text-sm);
18
+ color: var(--color-danger);
19
+ margin-top: var(--space-xs);
20
+ }
21
+
22
+ .success-message {
23
+ font-size: var(--text-sm);
24
+ color: var(--color-success);
25
+ margin-top: var(--space-xs);
26
+ }
27
+
28
+ /* --- Loading States --- */
29
+
30
+ .loading {
31
+ opacity: 0.6;
32
+ pointer-events: none;
33
+ position: relative;
34
+ }
35
+
36
+ .spinner {
37
+ display: inline-block;
38
+ width: 1em;
39
+ height: 1em;
40
+ border: 2px solid var(--color-border);
41
+ border-top-color: var(--color-primary);
42
+ border-radius: 50%;
43
+ animation: spin 0.6s linear infinite;
44
+ }
45
+
46
+ @keyframes spin {
47
+ to {
48
+ transform: rotate(360deg);
49
+ }
50
+ }
51
+
52
+ /* --- Icon Base --- */
53
+
54
+ .icon {
55
+ display: inline-flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ width: 1.25em;
59
+ height: 1.25em;
60
+ vertical-align: middle;
61
+ fill: currentColor;
62
+
63
+ & svg {
64
+ width: 100%;
65
+ height: 100%;
66
+ }
67
+ }
68
+
69
+ .icon-sm {
70
+ width: 1em;
71
+ height: 1em;
72
+ }
73
+ .icon-lg {
74
+ width: 1.5em;
75
+ height: 1.5em;
76
+ }
77
+
78
+ /* --- Screen Reader Only --- */
79
+
80
+ .sr-only {
81
+ position: absolute;
82
+ width: 1px;
83
+ height: 1px;
84
+ padding: 0;
85
+ margin: -1px;
86
+ overflow: hidden;
87
+ clip: rect(0, 0, 0, 0);
88
+ white-space: nowrap;
89
+ border: 0;
90
+ }
91
+
92
+ /* --- Common Transitions --- */
93
+
94
+ .fade-in {
95
+ animation: fadeIn 0.2s ease-in;
96
+ }
97
+
98
+ @keyframes fadeIn {
99
+ from {
100
+ opacity: 0;
101
+ }
102
+ to {
103
+ opacity: 1;
104
+ }
105
+ }
106
+
107
+ /* --- Badge / Status Indicators --- */
108
+
109
+ .badge {
110
+ display: inline-block;
111
+ padding: var(--space-xs) var(--space-sm);
112
+ font-size: var(--text-sm);
113
+ font-weight: 500;
114
+ border-radius: var(--radius-sm);
115
+ line-height: 1;
116
+ }
117
+
118
+ .badge-success {
119
+ background: color-mix(in srgb, var(--color-success) 15%, transparent);
120
+ color: var(--color-success);
121
+ }
122
+ .badge-warning {
123
+ background: color-mix(in srgb, var(--color-warning) 15%, transparent);
124
+ color: var(--color-warning);
125
+ }
126
+ .badge-danger {
127
+ background: color-mix(in srgb, var(--color-danger) 15%, transparent);
128
+ color: var(--color-danger);
129
+ }
130
+ .badge-info {
131
+ background: color-mix(in srgb, var(--color-info) 15%, transparent);
132
+ color: var(--color-info);
133
+ }
134
+
135
+ /* --- Form Shared --- see forms.css --- */
@@ -0,0 +1,65 @@
1
+ :root {
2
+ /* Colors — Light */
3
+ --color-primary: #2563eb;
4
+ --color-primary-hover: #1d4ed8;
5
+ --color-success: #16a34a;
6
+ --color-success-hover: #15803d;
7
+ --color-warning: #d97706;
8
+ --color-warning-hover: #b45309;
9
+ --color-danger: #dc2626;
10
+ --color-danger-hover: #b91c1c;
11
+ --color-info: #0284c7;
12
+ --color-info-hover: #0369a1;
13
+ --color-text: #1e293b;
14
+ --color-text-muted: #64748b;
15
+ --color-text-inverse: #ffffff;
16
+ --color-bg: #f8fafc;
17
+ --color-surface: #ffffff;
18
+ --color-border: #e2e8f0;
19
+
20
+ /* Spacing */
21
+ --space-xs: 0.25rem;
22
+ --space-sm: 0.5rem;
23
+ --space-md: 1rem;
24
+ --space-lg: 1.5rem;
25
+ --space-xl: 2rem;
26
+
27
+ /* Typography */
28
+ --font-sans: system-ui, -apple-system, sans-serif;
29
+ --font-mono: ui-monospace, monospace;
30
+ --text-sm: 0.875rem;
31
+ --text-base: 1rem;
32
+ --text-lg: 1.25rem;
33
+ --text-xl: 1.5rem;
34
+
35
+ /* Borders */
36
+ --radius-sm: 0.25rem;
37
+ --radius-md: 0.375rem;
38
+ --radius-lg: 0.5rem;
39
+
40
+ /* Shadows */
41
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
42
+ --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
43
+ }
44
+
45
+ /* Dark mode */
46
+ [data-theme='dark'] {
47
+ --color-primary: #3b82f6;
48
+ --color-primary-hover: #60a5fa;
49
+ --color-success: #22c55e;
50
+ --color-success-hover: #4ade80;
51
+ --color-warning: #f59e0b;
52
+ --color-warning-hover: #fbbf24;
53
+ --color-danger: #ef4444;
54
+ --color-danger-hover: #f87171;
55
+ --color-info: #38bdf8;
56
+ --color-info-hover: #7dd3fc;
57
+ --color-text: #e2e8f0;
58
+ --color-text-muted: #94a3b8;
59
+ --color-text-inverse: #0f172a;
60
+ --color-bg: #0f172a;
61
+ --color-surface: #1e293b;
62
+ --color-border: #334155;
63
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
64
+ --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.4);
65
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Date formatting helpers.
3
+ * @module utils/formatDate
4
+ */
5
+
6
+ /**
7
+ * Format an ISO 8601 string to a readable date.
8
+ * @param {string|null|undefined} iso - ISO date string
9
+ * @returns {string} Formatted date or empty string
10
+ */
11
+ export function formatDate(iso) {
12
+ if (!iso) return '';
13
+ const d = new Date(iso);
14
+ if (isNaN(d.getTime())) return '';
15
+ return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
16
+ }
17
+
18
+ /**
19
+ * Format an ISO 8601 string to relative time (e.g. "3 days ago").
20
+ * @param {string|null|undefined} iso - ISO date string
21
+ * @returns {string} Relative time or empty string
22
+ */
23
+ export function timeAgo(iso) {
24
+ if (!iso) return '';
25
+ const seconds = Math.floor((Date.now() - new Date(iso).getTime()) / 1000);
26
+ if (isNaN(seconds)) return '';
27
+
28
+ const intervals = [
29
+ { label: 'year', seconds: 31536000 },
30
+ { label: 'month', seconds: 2592000 },
31
+ { label: 'day', seconds: 86400 },
32
+ { label: 'hour', seconds: 3600 },
33
+ { label: 'minute', seconds: 60 },
34
+ ];
35
+
36
+ for (const { label, seconds: s } of intervals) {
37
+ const count = Math.floor(seconds / s);
38
+ if (count >= 1) return `${count} ${label}${count > 1 ? 's' : ''} ago`;
39
+ }
40
+ return 'just now';
41
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Maps entity status/priority values to badge colors and display titles.
3
+ * @module utils/statusColors
4
+ */
5
+
6
+ /** @type {Record<string, 'info'|'success'|'warning'|'danger'>} */
7
+ const STATUS_COLORS = {
8
+ active: 'success',
9
+ archived: 'info',
10
+ todo: 'info',
11
+ doing: 'warning',
12
+ done: 'success',
13
+ };
14
+
15
+ /** @type {Record<string, string>} */
16
+ const STATUS_TITLES = {
17
+ active: 'Active',
18
+ archived: 'Archived',
19
+ todo: 'To Do',
20
+ doing: 'In Progress',
21
+ done: 'Done',
22
+ };
23
+
24
+ /** @type {Record<string, 'info'|'success'|'warning'|'danger'>} */
25
+ const PRIORITY_COLORS = {
26
+ low: 'info',
27
+ med: 'warning',
28
+ high: 'danger',
29
+ };
30
+
31
+ /** @type {Record<string, string>} */
32
+ const PRIORITY_TITLES = {
33
+ low: 'Low',
34
+ med: 'Medium',
35
+ high: 'High',
36
+ };
37
+
38
+ /**
39
+ * @param {string} status
40
+ * @returns {'info'|'success'|'warning'|'danger'}
41
+ */
42
+ export const statusColor = (status) => STATUS_COLORS[status] || 'info';
43
+
44
+ /**
45
+ * @param {string} status
46
+ * @returns {string}
47
+ */
48
+ export const statusTitle = (status) => STATUS_TITLES[status] || status;
49
+
50
+ /**
51
+ * @param {string} priority
52
+ * @returns {'info'|'success'|'warning'|'danger'}
53
+ */
54
+ export const priorityColor = (priority) => PRIORITY_COLORS[priority] || 'info';
55
+
56
+ /**
57
+ * @param {string} priority
58
+ * @returns {string}
59
+ */
60
+ export const priorityTitle = (priority) => PRIORITY_TITLES[priority] || priority;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Home page — demonstrates localStorage-backed state.
3
+ * @module pages/home
4
+ */
5
+
6
+ import { html, define, store } from 'hybrids';
7
+ import AppState from '../../store/AppState.js';
8
+ import '../../components/atoms/app-button/app-button.js';
9
+
10
+ /**
11
+ * @typedef {Object} HomeViewHost
12
+ * @property {import('../../store/AppState.js').AppState} state
13
+ */
14
+
15
+ /** @param {HomeViewHost & HTMLElement} host */
16
+ function increment(host) {
17
+ if (!store.ready(host.state)) return;
18
+ store.set(host.state, { count: host.state.count + 1 });
19
+ }
20
+
21
+ export default define({
22
+ tag: 'home-view',
23
+ state: store(AppState),
24
+ render: {
25
+ value: ({ state }) => html`
26
+ <div class="home-view">
27
+ <h1>{{name}}</h1>
28
+ <p>Your Clearstack project is ready. Start building!</p>
29
+ ${store.ready(state) && html`
30
+ <p>Count: ${state.count}</p>
31
+ <button class="btn btn-primary" onclick="${increment}">Increment</button>
32
+ <p class="hint">State persists in localStorage — refresh to verify.</p>
33
+ `}
34
+ </div>
35
+ `,
36
+ shadow: false,
37
+ },
38
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * App router shell — manages view stack.
3
+ * @module router
4
+ */
5
+
6
+ import { html, define, router } from 'hybrids';
7
+ import HomeView from '../pages/home/home-view.js';
8
+
9
+ export default define({
10
+ tag: 'app-router',
11
+ stack: router(HomeView, { url: '/' }),
12
+ render: {
13
+ value: ({ stack }) => html`<div class="app-router">${stack}</div>`,
14
+ shadow: false,
15
+ },
16
+ });
@@ -0,0 +1,26 @@
1
+ /**
2
+ * App state — singleton, localStorage-backed. No server needed.
3
+ * @module store/AppState
4
+ */
5
+
6
+ import { store } from 'hybrids';
7
+
8
+ /** @typedef {{ theme: 'light'|'dark', count: number }} AppState */
9
+
10
+ /** @type {import('hybrids').Model<AppState>} */
11
+ const AppState = {
12
+ theme: 'light',
13
+ count: 0,
14
+ [store.connect]: {
15
+ get: () => {
16
+ const raw = localStorage.getItem('appState');
17
+ return raw ? JSON.parse(raw) : {};
18
+ },
19
+ set: (id, values) => {
20
+ localStorage.setItem('appState', JSON.stringify(values));
21
+ return values;
22
+ },
23
+ },
24
+ };
25
+
26
+ export default AppState;