@everystate/examples 1.0.0 → 1.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.
- package/README.md +15 -0
- package/everyState-core/001-counter/README.md +44 -0
- package/everyState-core/001-counter/index.html +79 -0
- package/everyState-core/002-counter-improved/README.md +44 -0
- package/everyState-core/002-counter-improved/index.html +83 -0
- package/everyState-core/003-input-reactive/README.md +44 -0
- package/everyState-core/003-input-reactive/index.html +68 -0
- package/everyState-core/004-computed-state/README.md +45 -0
- package/everyState-core/004-computed-state/index.html +83 -0
- package/everyState-core/005-conditional-rendering/README.md +42 -0
- package/everyState-core/005-conditional-rendering/index.html +68 -0
- package/everyState-core/006-list-rendering/README.md +49 -0
- package/everyState-core/006-list-rendering/index.html +92 -0
- package/everyState-core/007-form-validation/README.md +52 -0
- package/everyState-core/007-form-validation/index.html +108 -0
- package/everyState-core/008-undo-redo/README.md +70 -0
- package/everyState-core/008-undo-redo/index.html +133 -0
- package/everyState-core/009-localStorage-side-effects/README.md +72 -0
- package/everyState-core/009-localStorage-side-effects/index.html +80 -0
- package/everyState-core/010-decoupled-components/README.md +74 -0
- package/everyState-core/010-decoupled-components/index.html +117 -0
- package/everyState-core/011-async-patterns/README.md +98 -0
- package/everyState-core/011-async-patterns/index.html +132 -0
- package/everyState-css/001-stateDrivenCSS/index.html +377 -0
- package/everyState-css/002-cssV2FullDemo/index.html +630 -0
- package/everyState-view/001/counter/index.css +31 -0
- package/everyState-view/001/counter/index.html +50 -0
- package/everyState-view/002/datatable/index.css +70 -0
- package/everyState-view/002/datatable/index.html +118 -0
- package/everyState-view/003/todo/index.css +260 -0
- package/everyState-view/003/todo/index.html +218 -0
- package/everyState-view/003-input-reactive/README.md +44 -0
- package/everyState-view/003-input-reactive/index.html +68 -0
- package/everyState-view/004/quotesFetcher/index.css +124 -0
- package/everyState-view/004/quotesFetcher/index.html +108 -0
- package/everyState-view/004_01/quotesFetcher/app.js +32 -0
- package/everyState-view/004_01/quotesFetcher/components/appHeader/appSubtitle.js +2 -0
- package/everyState-view/004_01/quotesFetcher/components/appHeader/appTitle.js +2 -0
- package/everyState-view/004_01/quotesFetcher/components/appHeader.js +9 -0
- package/everyState-view/004_01/quotesFetcher/components/historyHeading.js +2 -0
- package/everyState-view/004_01/quotesFetcher/components/historyList/histAuthor.js +2 -0
- package/everyState-view/004_01/quotesFetcher/components/historyList/histQuote.js +2 -0
- package/everyState-view/004_01/quotesFetcher/components/historyList.js +14 -0
- package/everyState-view/004_01/quotesFetcher/components/quoteCard/fetchButton.js +2 -0
- package/everyState-view/004_01/quotesFetcher/components/quoteCard/quoteAuthor.js +2 -0
- package/everyState-view/004_01/quotesFetcher/components/quoteCard/quoteMessage.js +2 -0
- package/everyState-view/004_01/quotesFetcher/components/quoteCard/quoteText.js +2 -0
- package/everyState-view/004_01/quotesFetcher/components/quoteCard.js +11 -0
- package/everyState-view/004_01/quotesFetcher/index.css +124 -0
- package/everyState-view/004_01/quotesFetcher/index.html +23 -0
- package/everyState-view/004_01/quotesFetcher/store.js +35 -0
- package/everyState-view/004_02/quotesFetcher/app.js +20 -0
- package/everyState-view/004_02/quotesFetcher/components.js +46 -0
- package/everyState-view/004_02/quotesFetcher/index.css +124 -0
- package/everyState-view/004_02/quotesFetcher/index.html +23 -0
- package/everyState-view/004_02/quotesFetcher/store.js +35 -0
- package/everyState-view/004_03/quotesFetcher/actions.js +27 -0
- package/everyState-view/004_03/quotesFetcher/app.js +19 -0
- package/everyState-view/004_03/quotesFetcher/components.js +28 -0
- package/everyState-view/004_03/quotesFetcher/index.css +124 -0
- package/everyState-view/004_03/quotesFetcher/index.html +23 -0
- package/everyState-view/004_03/quotesFetcher/resolve.js +34 -0
- package/everyState-view/004_03/quotesFetcher/store.js +11 -0
- package/everyState-view/004_04/quotesFetcher/actions.js +66 -0
- package/everyState-view/004_04/quotesFetcher/app.js +24 -0
- package/everyState-view/004_04/quotesFetcher/components/archive.js +40 -0
- package/everyState-view/004_04/quotesFetcher/components/fetcher.js +29 -0
- package/everyState-view/004_04/quotesFetcher/components.js +20 -0
- package/everyState-view/004_04/quotesFetcher/index.css +283 -0
- package/everyState-view/004_04/quotesFetcher/index.html +24 -0
- package/everyState-view/004_04/quotesFetcher/resolve.js +34 -0
- package/everyState-view/004_04/quotesFetcher/store.js +21 -0
- package/everyState-view/004_04/statedump.json +826 -0
- package/everyState-view/004_05/quoteExplorer/actions.js +58 -0
- package/everyState-view/004_05/quoteExplorer/app.js +27 -0
- package/everyState-view/004_05/quoteExplorer/components.js +83 -0
- package/everyState-view/004_05/quoteExplorer/index.css +231 -0
- package/everyState-view/004_05/quoteExplorer/index.html +23 -0
- package/everyState-view/004_05/quoteExplorer/resolve.js +50 -0
- package/everyState-view/004_05/quoteExplorer/store.js +33 -0
- package/everyState-view/004_06/quoteExplorer/actions.js +21 -0
- package/everyState-view/004_06/quoteExplorer/app.js +44 -0
- package/everyState-view/004_06/quoteExplorer/components.js +80 -0
- package/everyState-view/004_06/quoteExplorer/derived.js +43 -0
- package/everyState-view/004_06/quoteExplorer/index.css +346 -0
- package/everyState-view/004_06/quoteExplorer/index.html +25 -0
- package/everyState-view/004_06/quoteExplorer/intents.js +44 -0
- package/everyState-view/004_06/quoteExplorer/policies.js +25 -0
- package/everyState-view/004_06/quoteExplorer/resolve.js +51 -0
- package/everyState-view/004_06/quoteExplorer/store.js +44 -0
- package/everyState-view/004_07/quoteExplorer/app.js +47 -0
- package/everyState-view/004_07/quoteExplorer/components.js +85 -0
- package/everyState-view/004_07/quoteExplorer/derived.js +43 -0
- package/everyState-view/004_07/quoteExplorer/index.css +346 -0
- package/everyState-view/004_07/quoteExplorer/index.html +25 -0
- package/everyState-view/004_07/quoteExplorer/intents.js +51 -0
- package/everyState-view/004_07/quoteExplorer/policies.js +21 -0
- package/everyState-view/004_07/quoteExplorer/resolve.js +39 -0
- package/everyState-view/004_07/quoteExplorer/store.js +44 -0
- package/everyState-view/004_08/quoteExplorer/app.js +78 -0
- package/everyState-view/004_08/quoteExplorer/components.js +85 -0
- package/everyState-view/004_08/quoteExplorer/derived.js +43 -0
- package/everyState-view/004_08/quoteExplorer/index.css +346 -0
- package/everyState-view/004_08/quoteExplorer/index.html +25 -0
- package/everyState-view/004_08/quoteExplorer/intents.js +51 -0
- package/everyState-view/004_08/quoteExplorer/policies.js +21 -0
- package/everyState-view/004_08/quoteExplorer/resolve.js +39 -0
- package/everyState-view/004_08/quoteExplorer/store.js +44 -0
- package/everyState-view/004_08_V2/app.js +78 -0
- package/everyState-view/004_08_V2/components/appDetail.js +8 -0
- package/everyState-view/004_08_V2/components/appDetailBar.js +7 -0
- package/everyState-view/004_08_V2/components/appDetailBarClose.js +8 -0
- package/everyState-view/004_08_V2/components/appDetailBarCount.js +7 -0
- package/everyState-view/004_08_V2/components/appDetailBarHeading.js +7 -0
- package/everyState-view/004_08_V2/components/appDetailQuotes.js +15 -0
- package/everyState-view/004_08_V2/components/appHeader.js +7 -0
- package/everyState-view/004_08_V2/components/appHeaderSubtitle.js +7 -0
- package/everyState-view/004_08_V2/components/appHeaderTitle.js +7 -0
- package/everyState-view/004_08_V2/components/appLog.js +7 -0
- package/everyState-view/004_08_V2/components/appLogHeading.js +7 -0
- package/everyState-view/004_08_V2/components/appLogList.js +16 -0
- package/everyState-view/004_08_V2/components/appSearch.js +7 -0
- package/everyState-view/004_08_V2/components/appSearchInput.js +9 -0
- package/everyState-view/004_08_V2/components/appStats.js +7 -0
- package/everyState-view/004_08_V2/components/appStatsContent.js +8 -0
- package/everyState-view/004_08_V2/components/appStatsContentFavcount.js +7 -0
- package/everyState-view/004_08_V2/components/appStatsContentText.js +7 -0
- package/everyState-view/004_08_V2/components/appStatsToggle.js +8 -0
- package/everyState-view/004_08_V2/components/appTags.js +7 -0
- package/everyState-view/004_08_V2/components/appTagsLabel.js +7 -0
- package/everyState-view/004_08_V2/components/appTagsRow.js +15 -0
- package/everyState-view/004_08_V2/components/index.js +59 -0
- package/everyState-view/004_08_V2/components/utils/css.js +88 -0
- package/everyState-view/004_08_V2/components/utils/elements.js +87 -0
- package/everyState-view/004_08_V2/components.js +79 -0
- package/everyState-view/004_08_V2/derived.js +43 -0
- package/everyState-view/004_08_V2/index.css +350 -0
- package/everyState-view/004_08_V2/index.html +25 -0
- package/everyState-view/004_08_V2/intents.js +51 -0
- package/everyState-view/004_08_V2/policies.js +21 -0
- package/everyState-view/004_08_V2/resolve.js +39 -0
- package/everyState-view/004_08_V2/store.js +44 -0
- package/everyState-view/006/api-datatable/index.css +388 -0
- package/everyState-view/006/api-datatable/index.html +355 -0
- package/everyState-view/007/apiUsers/index.html +307 -0
- package/everyState-view/007-form-validation/README.md +52 -0
- package/everyState-view/007-form-validation/index.html +108 -0
- package/everyState-view/010-decoupled-components/README.md +74 -0
- package/everyState-view/010-decoupled-components/index.html +117 -0
- package/everyState-view/index.html +36 -0
- package/index.js +0 -5
- package/package.json +2 -4
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// ─── Derived state ──────────────────────────────────────────────
|
|
2
|
+
// Subscribe to state.* and auto-compute derived values.
|
|
3
|
+
// Pure recomputation: reads state.*, writes derived.*.
|
|
4
|
+
// No intent knowledge, no view knowledge.
|
|
5
|
+
//
|
|
6
|
+
// KEY CHANGE from 004_06: uses setMany() for atomic batch writes.
|
|
7
|
+
|
|
8
|
+
export function mountDerived(store) {
|
|
9
|
+
function recompute() {
|
|
10
|
+
const quotes = store.get('state.quotes') || [];
|
|
11
|
+
const author = store.get('state.selectedAuthor') || '';
|
|
12
|
+
const search = (store.get('state.searchTerm') || '').toLowerCase().trim();
|
|
13
|
+
|
|
14
|
+
// Filter: only when an author is selected
|
|
15
|
+
let filtered = [];
|
|
16
|
+
if (author) {
|
|
17
|
+
filtered = quotes.filter(q => q.author === author);
|
|
18
|
+
if (search) {
|
|
19
|
+
filtered = filtered.filter(q =>
|
|
20
|
+
q.text.toLowerCase().includes(search)
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const totalAuthors = (store.get('state.authors') || []).length;
|
|
26
|
+
const favTotal = quotes.filter(q => q.fav).length;
|
|
27
|
+
|
|
28
|
+
store.setMany({
|
|
29
|
+
'derived.filteredQuotes': filtered,
|
|
30
|
+
'derived.quoteCount': filtered.length,
|
|
31
|
+
'derived.favCount': favTotal,
|
|
32
|
+
'derived.stats': `${quotes.length} total quotes · ${totalAuthors} authors · ${favTotal} favorites`,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Recompute whenever any state.* path changes
|
|
37
|
+
const unsub = store.subscribe('state.*', recompute);
|
|
38
|
+
|
|
39
|
+
// Initial computation
|
|
40
|
+
recompute();
|
|
41
|
+
|
|
42
|
+
return unsub;
|
|
43
|
+
}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/* ─── 004_06 Quote Explorer Pro ─────────────────────────────── */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--bg: #0f172a;
|
|
5
|
+
--surface: #1e293b;
|
|
6
|
+
--surface-alt: #334155;
|
|
7
|
+
--border: #475569;
|
|
8
|
+
--text: #e2e8f0;
|
|
9
|
+
--text-muted: #94a3b8;
|
|
10
|
+
--accent: #38bdf8;
|
|
11
|
+
--accent-hover: #7dd3fc;
|
|
12
|
+
--danger: #ef4444;
|
|
13
|
+
--fav: #f43f5e;
|
|
14
|
+
--fav-muted: #64748b;
|
|
15
|
+
--success: #22c55e;
|
|
16
|
+
--radius: 8px;
|
|
17
|
+
--shadow: 0 4px 16px rgba(0,0,0,.3);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
21
|
+
|
|
22
|
+
body {
|
|
23
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
24
|
+
background: var(--bg);
|
|
25
|
+
color: var(--text);
|
|
26
|
+
line-height: 1.6;
|
|
27
|
+
min-height: 100vh;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.mb-1 { margin-bottom: 1rem; }
|
|
31
|
+
.mb-1_5 { margin-bottom: 1.5rem; }
|
|
32
|
+
|
|
33
|
+
/* ── App layout ── */
|
|
34
|
+
|
|
35
|
+
.app {
|
|
36
|
+
max-width: 720px;
|
|
37
|
+
margin: 0 auto;
|
|
38
|
+
padding: 2rem 1.5rem 4rem;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* ── Header ── */
|
|
42
|
+
|
|
43
|
+
.app-header {
|
|
44
|
+
text-align: center;
|
|
45
|
+
margin-bottom: 2rem;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.app-header h1 {
|
|
49
|
+
font-size: 2rem;
|
|
50
|
+
font-weight: 700;
|
|
51
|
+
background: linear-gradient(135deg, var(--accent), #a78bfa);
|
|
52
|
+
-webkit-background-clip: text;
|
|
53
|
+
-webkit-text-fill-color: transparent;
|
|
54
|
+
background-clip: text;
|
|
55
|
+
margin: 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.subtitle {
|
|
59
|
+
color: var(--text-muted);
|
|
60
|
+
font-size: 0.85rem;
|
|
61
|
+
margin-top: 0.25rem;
|
|
62
|
+
letter-spacing: 0.04em;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* ── Search bar ── */
|
|
66
|
+
|
|
67
|
+
.search-bar {
|
|
68
|
+
margin-bottom: 1.5rem;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.search-input {
|
|
72
|
+
width: 100%;
|
|
73
|
+
padding: 0.7rem 1rem;
|
|
74
|
+
background: var(--surface);
|
|
75
|
+
color: var(--text);
|
|
76
|
+
border: 1px solid var(--border);
|
|
77
|
+
border-radius: var(--radius);
|
|
78
|
+
font-size: 0.95rem;
|
|
79
|
+
outline: none;
|
|
80
|
+
transition: border-color 0.2s;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.search-input::placeholder {
|
|
84
|
+
color: var(--text-muted);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.search-input:focus {
|
|
88
|
+
border-color: var(--accent);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* ── Author tags ── */
|
|
92
|
+
|
|
93
|
+
.author-tags {
|
|
94
|
+
margin-bottom: 1.5rem;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.tags-label {
|
|
98
|
+
display: block;
|
|
99
|
+
font-size: 0.8rem;
|
|
100
|
+
color: var(--text-muted);
|
|
101
|
+
margin-bottom: 0.5rem;
|
|
102
|
+
text-transform: uppercase;
|
|
103
|
+
letter-spacing: 0.06em;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.tags-row {
|
|
107
|
+
display: flex;
|
|
108
|
+
flex-wrap: wrap;
|
|
109
|
+
gap: 0.5rem;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.tag {
|
|
113
|
+
padding: 0.4rem 0.9rem;
|
|
114
|
+
background: var(--surface);
|
|
115
|
+
color: var(--accent);
|
|
116
|
+
border: 1px solid var(--border);
|
|
117
|
+
border-radius: 999px;
|
|
118
|
+
font-size: 0.85rem;
|
|
119
|
+
cursor: pointer;
|
|
120
|
+
transition: all 0.15s;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.tag:hover {
|
|
124
|
+
background: var(--accent);
|
|
125
|
+
color: var(--bg);
|
|
126
|
+
border-color: var(--accent);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* ── Detail panel ── */
|
|
130
|
+
|
|
131
|
+
.detail-panel {
|
|
132
|
+
background: var(--surface);
|
|
133
|
+
border: 1px solid var(--border);
|
|
134
|
+
border-radius: var(--radius);
|
|
135
|
+
padding: 1.25rem;
|
|
136
|
+
margin-bottom: 1.5rem;
|
|
137
|
+
animation: slideIn 0.25s ease-out;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@keyframes slideIn {
|
|
141
|
+
from { opacity: 0; transform: translateY(-8px); }
|
|
142
|
+
to { opacity: 1; transform: translateY(0); }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.detail-bar {
|
|
146
|
+
display: flex;
|
|
147
|
+
align-items: center;
|
|
148
|
+
gap: 0.75rem;
|
|
149
|
+
margin-bottom: 1rem;
|
|
150
|
+
padding-bottom: 0.75rem;
|
|
151
|
+
border-bottom: 1px solid var(--border);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.detail-heading {
|
|
155
|
+
font-size: 1.2rem;
|
|
156
|
+
font-weight: 600;
|
|
157
|
+
flex: 1;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.badge {
|
|
161
|
+
font-size: 0.75rem;
|
|
162
|
+
padding: 0.2rem 0.6rem;
|
|
163
|
+
background: var(--accent);
|
|
164
|
+
color: var(--bg);
|
|
165
|
+
border-radius: 999px;
|
|
166
|
+
font-weight: 600;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.detail-quotes {
|
|
170
|
+
list-style: none;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.quote-item {
|
|
174
|
+
display: flex;
|
|
175
|
+
align-items: flex-start;
|
|
176
|
+
gap: 0.75rem;
|
|
177
|
+
padding: 0.6rem 0;
|
|
178
|
+
border-bottom: 1px solid rgba(255,255,255,0.05);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.quote-item:last-child {
|
|
182
|
+
border-bottom: none;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.quote-text {
|
|
186
|
+
flex: 1;
|
|
187
|
+
font-size: 0.92rem;
|
|
188
|
+
color: var(--text);
|
|
189
|
+
line-height: 1.5;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.fav-btn {
|
|
193
|
+
background: none;
|
|
194
|
+
border: none;
|
|
195
|
+
font-size: 1.1rem;
|
|
196
|
+
cursor: pointer;
|
|
197
|
+
color: var(--fav-muted);
|
|
198
|
+
transition: color 0.15s, transform 0.15s;
|
|
199
|
+
padding: 0.2rem;
|
|
200
|
+
line-height: 1;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.fav-btn:hover {
|
|
204
|
+
color: var(--fav);
|
|
205
|
+
transform: scale(1.2);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.fav-btn.is-fav {
|
|
209
|
+
color: var(--fav);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* ── Buttons ── */
|
|
213
|
+
|
|
214
|
+
.btn {
|
|
215
|
+
padding: 0.45rem 0.9rem;
|
|
216
|
+
border: 1px solid var(--border);
|
|
217
|
+
border-radius: var(--radius);
|
|
218
|
+
background: var(--surface);
|
|
219
|
+
color: var(--text);
|
|
220
|
+
font-size: 0.85rem;
|
|
221
|
+
cursor: pointer;
|
|
222
|
+
transition: all 0.15s;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.btn:hover {
|
|
226
|
+
background: var(--surface-alt);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.btn-close {
|
|
230
|
+
font-size: 1rem;
|
|
231
|
+
padding: 0.2rem 0.5rem;
|
|
232
|
+
color: var(--text-muted);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.btn-close:hover {
|
|
236
|
+
color: var(--danger);
|
|
237
|
+
border-color: var(--danger);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.btn-secondary {
|
|
241
|
+
background: var(--surface);
|
|
242
|
+
color: var(--accent);
|
|
243
|
+
border-color: var(--accent);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.btn-secondary:hover {
|
|
247
|
+
background: var(--accent);
|
|
248
|
+
color: var(--bg);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* ── Stats section ── */
|
|
252
|
+
|
|
253
|
+
.stats-section {
|
|
254
|
+
margin-bottom: 1.5rem;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.stats-content {
|
|
258
|
+
margin-top: 0.75rem;
|
|
259
|
+
padding: 1rem;
|
|
260
|
+
background: var(--surface);
|
|
261
|
+
border: 1px solid var(--border);
|
|
262
|
+
border-radius: var(--radius);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.stats-text {
|
|
266
|
+
font-size: 0.9rem;
|
|
267
|
+
color: var(--text-muted);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.stats-fav {
|
|
271
|
+
font-size: 0.9rem;
|
|
272
|
+
color: var(--fav);
|
|
273
|
+
margin-top: 0.3rem;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* ── Activity log ── */
|
|
277
|
+
|
|
278
|
+
.log-section {
|
|
279
|
+
margin-top: 1rem;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.log-heading {
|
|
283
|
+
font-size: 0.95rem;
|
|
284
|
+
color: var(--text-muted);
|
|
285
|
+
margin-bottom: 0.5rem;
|
|
286
|
+
font-weight: 600;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.log-list {
|
|
290
|
+
list-style: none;
|
|
291
|
+
max-height: 260px;
|
|
292
|
+
overflow-y: auto;
|
|
293
|
+
background: var(--surface);
|
|
294
|
+
border: 1px solid var(--border);
|
|
295
|
+
border-radius: var(--radius);
|
|
296
|
+
padding: 0.5rem;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.log-entry {
|
|
300
|
+
display: flex;
|
|
301
|
+
gap: 0.6rem;
|
|
302
|
+
padding: 0.35rem 0.5rem;
|
|
303
|
+
font-size: 0.78rem;
|
|
304
|
+
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
|
305
|
+
border-bottom: 1px solid rgba(255,255,255,0.04);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.log-entry:last-child {
|
|
309
|
+
border-bottom: none;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.log-time {
|
|
313
|
+
color: var(--text-muted);
|
|
314
|
+
min-width: 70px;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.log-intent {
|
|
318
|
+
color: var(--accent);
|
|
319
|
+
font-weight: 600;
|
|
320
|
+
min-width: 110px;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.log-value {
|
|
324
|
+
color: var(--text);
|
|
325
|
+
opacity: 0.7;
|
|
326
|
+
overflow: hidden;
|
|
327
|
+
text-overflow: ellipsis;
|
|
328
|
+
white-space: nowrap;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/* ── Utility ── */
|
|
332
|
+
|
|
333
|
+
.hidden {
|
|
334
|
+
display: none !important;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/* ── Scrollbar ── */
|
|
338
|
+
|
|
339
|
+
.log-list::-webkit-scrollbar {
|
|
340
|
+
width: 5px;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.log-list::-webkit-scrollbar-track {
|
|
344
|
+
background: var(--surface);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.log-list::-webkit-scrollbar-thumb {
|
|
348
|
+
background: var(--border);
|
|
349
|
+
border-radius: 3px;
|
|
350
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>004_08 - Quote Explorer Pro (transparent auto-intent)</title>
|
|
7
|
+
<link rel="stylesheet" href="index.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="app"></div>
|
|
11
|
+
|
|
12
|
+
<script type="importmap">
|
|
13
|
+
{
|
|
14
|
+
"imports": {
|
|
15
|
+
"@everystate/core": "/everystate-core/index.js",
|
|
16
|
+
"@everystate/core/queryClient": "/everystate-core/queryClient.js",
|
|
17
|
+
"@everystate/view/resolve": "/everystate-view/resolve.js",
|
|
18
|
+
"@everystate/view/project": "/everystate-view/project.js",
|
|
19
|
+
"@everystate/perf": "/everystate-perf/index.js"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
</script>
|
|
23
|
+
<script type="module" src="app.js"></script>
|
|
24
|
+
</body>
|
|
25
|
+
</html>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// ─── Intent handlers ────────────────────────────────────────────
|
|
2
|
+
// Subscribe to intent.* paths and handle business logic.
|
|
3
|
+
// UI events emit intents directly (no actions.js intermediary).
|
|
4
|
+
// Refs are injected so handlers can toggle view node classes.
|
|
5
|
+
//
|
|
6
|
+
// KEY CHANGES from 004_06:
|
|
7
|
+
// - setMany() for atomic batching (fewer subscriber fires)
|
|
8
|
+
// - Cleaner unsubs with const off = [...] pattern
|
|
9
|
+
|
|
10
|
+
export function mountIntents(store, refs) {
|
|
11
|
+
const off = [
|
|
12
|
+
|
|
13
|
+
// ── Select author -> show detail panel ──
|
|
14
|
+
store.subscribe('intent.selectAuthor', (author) => {
|
|
15
|
+
store.setMany({
|
|
16
|
+
'state.selectedAuthor': author,
|
|
17
|
+
[`view.nodes.${refs.detail}.class`]: 'detail-panel',
|
|
18
|
+
});
|
|
19
|
+
}),
|
|
20
|
+
|
|
21
|
+
// ── Close detail -> hide panel, clear search ──
|
|
22
|
+
store.subscribe('intent.closeDetail', () => {
|
|
23
|
+
store.setMany({
|
|
24
|
+
'state.selectedAuthor': '',
|
|
25
|
+
'state.searchTerm': '',
|
|
26
|
+
[`view.nodes.${refs.detail}.class`]: 'detail-panel hidden',
|
|
27
|
+
});
|
|
28
|
+
}),
|
|
29
|
+
|
|
30
|
+
// ── Toggle favorite on a quote ──
|
|
31
|
+
store.subscribe('intent.toggleFav', (id) => {
|
|
32
|
+
const quotes = store.get('state.quotes') || [];
|
|
33
|
+
store.set('state.quotes', quotes.map(q =>
|
|
34
|
+
q.id === id ? { ...q, fav: !q.fav } : q
|
|
35
|
+
));
|
|
36
|
+
}),
|
|
37
|
+
|
|
38
|
+
// ── Toggle stats visibility ──
|
|
39
|
+
store.subscribe('intent.toggleStats', () => {
|
|
40
|
+
const visible = store.get('ui.statsVisible');
|
|
41
|
+
store.setMany({
|
|
42
|
+
'ui.statsVisible': !visible,
|
|
43
|
+
[`view.nodes.${refs.statsContent}.class`]: visible ? 'stats-content hidden' : 'stats-content',
|
|
44
|
+
'ui.toggleLabel': visible ? '📊 Show Stats' : '📊 Hide Stats',
|
|
45
|
+
});
|
|
46
|
+
}),
|
|
47
|
+
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
return () => off.forEach(u => u());
|
|
51
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// ─── Policy subscriptions ───────────────────────────────────────
|
|
2
|
+
// Cross-cutting concerns via wildcard subscribers.
|
|
3
|
+
// intent.* captures every intent and logs it to the activity feed.
|
|
4
|
+
// This is the "policy layer" pattern from the book (Ch 12, 17).
|
|
5
|
+
|
|
6
|
+
export function mountPolicies(store) {
|
|
7
|
+
// Activity log: capture all intents via wildcard
|
|
8
|
+
return store.subscribe('intent.*', ({ path, value }) => {
|
|
9
|
+
const log = store.get('derived.activityLog') || [];
|
|
10
|
+
const intent = path.replace('intent.', '');
|
|
11
|
+
const display = typeof value === 'object'
|
|
12
|
+
? JSON.stringify(value)
|
|
13
|
+
: String(value ?? '');
|
|
14
|
+
const entry = {
|
|
15
|
+
time: new Date().toLocaleTimeString(),
|
|
16
|
+
intent,
|
|
17
|
+
value: display.length > 30 ? display.slice(0, 30) + '…' : display,
|
|
18
|
+
};
|
|
19
|
+
store.set('derived.activityLog', [entry, ...log].slice(0, 15));
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// ─── Resolve utilities ──────────────────────────────────────────
|
|
2
|
+
// Reduced from 004_06: resolveActions is gone (no actions.js).
|
|
3
|
+
// Only resolveTree + buildRefs remain.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* resolveTree(registry, key)
|
|
7
|
+
* Recursively builds a nested spec from the flat dot-path registry.
|
|
8
|
+
* String references in children/template are replaced with resolved specs.
|
|
9
|
+
*/
|
|
10
|
+
export function resolveTree(registry, key) {
|
|
11
|
+
const spec = registry[key];
|
|
12
|
+
if (!spec) return null;
|
|
13
|
+
const resolved = { ...spec };
|
|
14
|
+
|
|
15
|
+
if (Array.isArray(resolved.children)) {
|
|
16
|
+
resolved.children = resolved.children.map(childKey =>
|
|
17
|
+
typeof childKey === 'string' ? resolveTree(registry, childKey) : childKey
|
|
18
|
+
).filter(Boolean);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (typeof resolved.template === 'string') {
|
|
22
|
+
resolved.template = registry[resolved.template] || resolved.template;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return resolved;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* buildRefs(nodes)
|
|
30
|
+
* Scans normalized view nodes for ref properties.
|
|
31
|
+
* Returns { refName -> nodeId } so intent handlers can target view nodes.
|
|
32
|
+
*/
|
|
33
|
+
export function buildRefs(nodes) {
|
|
34
|
+
const refs = {};
|
|
35
|
+
for (const [id, node] of Object.entries(nodes)) {
|
|
36
|
+
if (node.ref) refs[node.ref] = id;
|
|
37
|
+
}
|
|
38
|
+
return refs;
|
|
39
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// ─── Store: namespaced state tree ───────────────────────────────
|
|
2
|
+
// state.* = source of truth (user data)
|
|
3
|
+
// derived.* = auto-computed by subscribers
|
|
4
|
+
// ui.* = ephemeral UI flags
|
|
5
|
+
// intent.* = written directly by UI events, consumed by intent handlers
|
|
6
|
+
|
|
7
|
+
import { createEveryState } from '@everystate/core';
|
|
8
|
+
|
|
9
|
+
export const store = createEveryState({
|
|
10
|
+
state: {
|
|
11
|
+
quotes: [
|
|
12
|
+
{ id: 1, author: 'Rumi', text: 'Your heart is the size of an ocean. Go find yourself in its hidden depths.', fav: false },
|
|
13
|
+
{ id: 2, author: 'Rumi', text: 'Respond to every call that excites your spirit.', fav: false },
|
|
14
|
+
{ id: 3, author: 'Rumi', text: 'Everything in the universe is within you. Ask all from yourself.', fav: false },
|
|
15
|
+
{ id: 4, author: 'Rumi', text: 'When I am silent, I have thunder hidden inside.', fav: false },
|
|
16
|
+
{ id: 5, author: 'Einstein', text: 'Imagination is more important than knowledge.', fav: false },
|
|
17
|
+
{ id: 6, author: 'Einstein', text: 'Life is like riding a bicycle. To keep your balance, you must keep moving.', fav: false },
|
|
18
|
+
{ id: 7, author: 'Einstein', text: "If you can't explain it simply, you don't understand it well enough.", fav: false },
|
|
19
|
+
{ id: 8, author: 'Mother Teresa', text: 'Spread love everywhere you go.', fav: false },
|
|
20
|
+
{ id: 9, author: 'Mother Teresa', text: 'If you judge people, you have no time to love them.', fav: false },
|
|
21
|
+
{ id: 10, author: 'Mother Teresa', text: 'Not all of us can do great things. But we can do small things with great love.', fav: false },
|
|
22
|
+
{ id: 11, author: 'Muhammad Ali', text: 'Float like a butterfly, sting like a bee.', fav: false },
|
|
23
|
+
{ id: 12, author: 'Muhammad Ali', text: "Don't count the days, make the days count.", fav: false },
|
|
24
|
+
{ id: 13, author: 'Muhammad Ali', text: 'Service to others is the rent you pay for your room here on earth.', fav: false },
|
|
25
|
+
{ id: 14, author: 'Socrates', text: 'The unexamined life is not worth living.', fav: false },
|
|
26
|
+
{ id: 15, author: 'Socrates', text: 'I know that I know nothing.', fav: false },
|
|
27
|
+
{ id: 16, author: 'Socrates', text: 'Wonder is the beginning of wisdom.', fav: false },
|
|
28
|
+
],
|
|
29
|
+
authors: ['Rumi', 'Einstein', 'Mother Teresa', 'Muhammad Ali', 'Socrates'],
|
|
30
|
+
selectedAuthor: '',
|
|
31
|
+
searchTerm: '',
|
|
32
|
+
},
|
|
33
|
+
derived: {
|
|
34
|
+
filteredQuotes: [],
|
|
35
|
+
quoteCount: 0,
|
|
36
|
+
favCount: 0,
|
|
37
|
+
stats: '',
|
|
38
|
+
activityLog: [],
|
|
39
|
+
},
|
|
40
|
+
ui: {
|
|
41
|
+
statsVisible: false,
|
|
42
|
+
toggleLabel: '📊 Show Stats',
|
|
43
|
+
},
|
|
44
|
+
});
|