@noego/app 0.0.7 → 0.0.10

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.
@@ -1,10 +1,17 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
- "Bash(cat:*)",
5
- "Bash(curl:*)",
6
- "Bash(noego dev)",
7
- "WebSearch"
4
+ "Read(//Users/shavauhngabay/dev/noego/dinner/**)",
5
+ "Read(//Users/shavauhngabay/dev/ego/sqlstack/**)",
6
+ "Read(//Users/shavauhngabay/dev/ego/forge/**)",
7
+ "Read(//Users/shavauhngabay/dev/noblelaw/ui/**)",
8
+ "Read(//Users/shavauhngabay/dev/noblelaw/**)",
9
+ "mcp__chrome-devtools__take_screenshot",
10
+ "Bash(npm run build:ui:ssr:*)",
11
+ "mcp__chrome-devtools__navigate_page",
12
+ "Bash(node dist/hammer.js:*)",
13
+ "Bash(tee:*)",
14
+ "mcp__chrome-devtools__list_console_messages"
8
15
  ],
9
16
  "deny": [],
10
17
  "ask": []
package/DEVELOPING.md ADDED
@@ -0,0 +1,73 @@
1
+ **Purpose**
2
+ - Run and test dev watchers without blocking your terminal.
3
+ - Verify backend/frontend restarts on file changes using short, backgrounded runs with logs and PIDs.
4
+
5
+ **Prerequisites**
6
+ - Node 20+.
7
+ - This repo installed (run `npm install` once).
8
+
9
+ **Local Watch Harness (Fast, Backgrounded)**
10
+ - Start (runs ~10–15s, then exits on its own):
11
+ - `Timestamp=$(date +%s); LOG=watch-harness-$Timestamp.log; node scripts/watch-harness.mjs > $LOG 2>&1 & echo $! > watch-harness.pid`
12
+ - Observe:
13
+ - `tail -f $LOG`
14
+ - Look for lines like:
15
+ - `🔄 change detected (change): server/stitch.yaml` (BACKEND)
16
+ - `🔄 change detected (change): hammer.config.yml` (SHARED)
17
+ - `🚀 [backend restart #X]` / `🚀 [frontend restart #X]`
18
+ - `✅ restart sequence complete`
19
+ - Stop (if ever needed):
20
+ - `kill $(cat watch-harness.pid)`
21
+ - What it does
22
+ - Seeds a temp tree under `.watch-harness/`.
23
+ - Watches directories (server/ui/middleware/openapi/repo) and classifies events using your globs.
24
+ - Spawns mock backend/frontend child processes, simulates backend/frontend/shared changes, restarts the right process(es), then shuts down.
25
+
26
+ **Real App Dev (noblelaw) in Background**
27
+ - Start dev for the noblelaw project in the background with logs:
28
+ - `NO_COLOR=1 node ./bin/app.js dev --root /Users/shavauhngabay/dev/noblelaw > noblelaw-dev.log 2>&1 & echo $! > noblelaw-dev.pid`
29
+ - Observe:
30
+ - `tail -f noblelaw-dev.log`
31
+ - Expect on startup: backend on port 3002, frontend (Vite) on 3001, router on 3000.
32
+ - Make edits to trigger restarts:
33
+ - Backend restart: edit `server/services/*.ts`, `middleware/**/*.ts`, `server/openapi/**/*.yaml`, or `server/repo/**/*.sql`.
34
+ - Frontend restart: edit `ui/**/*.ts`, `ui/openapi/**/*.yaml`.
35
+ - Shared restart: edit `index.ts` or `hammer.config.yml`.
36
+ - Note: `.svelte` changes do not restart the server; Vite HMR handles them.
37
+ - Look for logs:
38
+ - `🔄 FILE CHANGE DETECTED: …`
39
+ - `Type: BACKEND | FRONTEND | SHARED`
40
+ - `🚀 [RESTART #…] Starting …`
41
+ - `✅ Restart complete`
42
+ - Stop:
43
+ - `kill $(cat noblelaw-dev.pid)`
44
+ - Free busy ports (if you see EADDRINUSE):
45
+ - `for p in 3000 3001 3002; do P=$(lsof -t -nP -iTCP:$p -sTCP:LISTEN 2>/dev/null); [ -n "$P" ] && kill -9 $P; done`
46
+
47
+ **How The Watcher Works**
48
+ - Chokidar now watches concrete directories/files so it always attaches:
49
+ - Directories: `server/`, `ui/`, `middleware/`, `server/openapi/`, `server/repo/`, `ui/openapi/`.
50
+ - Files: `index.ts`, `hammer.config.yml`, `server/stitch.yaml`, `ui/stitch.yaml`.
51
+ - Classification still uses your original globs (picomatch):
52
+ - Backend: `server/**/*.ts`, `middleware/**/*.ts`, `server/stitch.yaml`, `server/openapi/**/*.yaml`, `server/repo/**/*.sql`.
53
+ - Frontend: `ui/**/*.ts`, `ui/stitch.yaml`, `ui/openapi/**/*.yaml`.
54
+ - Shared: `index.ts`, `hammer.config.yml` (plus any app-level watch entries).
55
+ - This avoids the “ready but no watchers” problem when globs don’t match initial files.
56
+
57
+ **Troubleshooting**
58
+ - No restart logs after editing a file:
59
+ - Confirm the file lives under a watched directory and matches one of the classification globs above.
60
+ - `.svelte` isn’t supposed to restart—use Vite HMR in the browser.
61
+ - Watcher error (EMFILE or similar):
62
+ - The CLI now fails fast on watcher errors with a clear log and exit.
63
+ - Ensure you’re not watching the entire repo root or `node_modules/`; keep to `server/`, `ui/`, `middleware/` and the specific YAML/SQL roots.
64
+ - Ports in use (EADDRINUSE):
65
+ - Free 3000/3001/3002 using the snippet above and restart dev.
66
+ - Clean up stray mock processes from the harness (if any):
67
+ - `pkill -f "\[backend\]"; pkill -f "\[frontend\]"`
68
+
69
+ **Operational Tips**
70
+ - Always background long-running scripts and write the PID to a file.
71
+ - Tail logs for verification and kill by PID when done.
72
+ - Keep a hard timeout in any custom test harness so it self-exits if something goes wrong.
73
+
@@ -0,0 +1,381 @@
1
+ # Asset Serving Architecture Investigation
2
+
3
+ **Status:** Under Investigation
4
+ **Date:** 2025-01-11 (Updated)
5
+ **Author:** Engineering Team
6
+ **Context:** Deep dive after initial runtime.js fix revealed deeper Forge integration issue
7
+
8
+ ---
9
+
10
+ ## Investigation Summary
11
+
12
+ After fixing the initial asset serving issue in `runtime.js` (mounting `.app` at root `/`), we discovered a **deeper architectural problem**: The browser is loading SSR components instead of client-bundled components, causing `effect_orphan` errors.
13
+
14
+ ---
15
+
16
+ ## What We Know FOR SURE (Confirmed via Code Review & Logs)
17
+
18
+ ### 1. Build Output Structure
19
+ ```
20
+ dist/
21
+ ├── .app/
22
+ │ ├── assets/ ← Client-bundled components (Vite output)
23
+ │ │ ├── chunks/ ← Shared code chunks
24
+ │ │ └── components/ ← Browser-ready .js files
25
+ │ │ ├── layout/
26
+ │ │ │ └── root.js ← VITE-BUNDLED, imports from ../../chunks/
27
+ │ │ └── views/
28
+ │ │ └── home.js
29
+ │ └── ssr/ ← SSR components (server-side)
30
+ │ ├── chunks/
31
+ │ └── components/
32
+ │ ├── layout/
33
+ │ │ └── root.js ← SSR VERSION, bare imports fail in browser
34
+ │ └── views/
35
+ │ └── home.js
36
+ ├── no_ego.js ← Bootstrap config
37
+ └── [compiled app files]
38
+ ```
39
+
40
+ ### 2. Generated Config Values (from `no_ego.js`)
41
+ ```javascript
42
+ {
43
+ "component_dir": ".app/ssr/components",
44
+ "component_dir_ssr": ".app/ssr/components",
45
+ "component_dir_client": "/assets/components",
46
+ "component_base_path": "/assets",
47
+ "assets_build_dir": ".app/assets/components",
48
+ "component_suffix": "components"
49
+ }
50
+ ```
51
+
52
+ ### 3. Express Static Mounts (4 total)
53
+
54
+ **Mount 1** (`runtime.js` line 701-702):
55
+ ```javascript
56
+ app.use('/client', express.static('/dist/.app/assets'))
57
+ ```
58
+
59
+ **Mount 2** (`runtime.js` line 703-704):
60
+ ```javascript
61
+ app.use('/chunks', express.static('/dist/.app/ssr/chunks'))
62
+ ```
63
+
64
+ **Mount 3** (Forge `server.ts` via `client.js` assets):
65
+ ```javascript
66
+ app.use('/', express.static('/dist/.app/assets'))
67
+ app.use('/assets', express.static('/dist/.app/assets'))
68
+ ```
69
+
70
+ **Mount 4** (Forge `server.ts` line 135-137):
71
+ ```javascript
72
+ app.use('/.app/ssr/components', express.static('/dist/.app/ssr/components'))
73
+ ```
74
+
75
+ ### 4. Browser Behavior (Confirmed via Network Tab)
76
+ ```
77
+ Browser requests: http://localhost:3050/.app/ssr/components/layout/root.js
78
+ Server responds: 200 OK (serves SSR component file)
79
+ Browser executes: SSR JavaScript with bare imports
80
+ Result: Module not found errors + effect_orphan error
81
+ ```
82
+
83
+ ### 5. Forge's Component Directory Injection (Line 264)
84
+ ```typescript
85
+ // express_server_adapter.ts:264
86
+ const clientComponentDir = this.isProd ? this.componentDir : '/assets';
87
+
88
+ // Line 281:
89
+ window.__COMPONENT_DIR__ = ${JSON.stringify(clientComponentDir)}
90
+ ```
91
+
92
+ **In production mode:**
93
+ - `this.componentDir` = `".app/ssr/components"` (passed from hammer/client.js)
94
+ - Therefore: `window.__COMPONENT_DIR__ = ".app/ssr/components"`
95
+ - Browser constructs URLs: `/.app/ssr/components/layout/root.js`
96
+
97
+ ### 6. Forge's Single component_dir Design (Confirmed)
98
+ **File:** `/Users/shavauhngabay/dev/forge/src/options/ServerOptions.ts`
99
+ ```typescript
100
+ export interface ServerOptions {
101
+ component_dir?: string; // SINGLE option for BOTH SSR and client
102
+ // NO component_dir_ssr
103
+ // NO component_dir_client
104
+ }
105
+ ```
106
+
107
+ **Usage in `server.ts` line 67:**
108
+ ```typescript
109
+ const COMPONENT_DIR = !full_options.component_dir ? root : path.join(root, full_options.component_dir);
110
+ ```
111
+
112
+ **Used for:**
113
+ - SSR: `ProdComponentLoader(COMPONENT_DIR)` - loads components server-side
114
+ - Client: Passed to `ExpressServerAdapter` → becomes `window.__COMPONENT_DIR__`
115
+ - Static serving: `app.use(\`/\${component_dir}\`, express.static(COMPONENT_DIR))`
116
+
117
+ ---
118
+
119
+ ## The Core Problem
120
+
121
+ ### What's Happening
122
+ 1. **hammer/client.js** passes: `component_dir: '.app/ssr/components'`
123
+ 2. **Forge** uses this for EVERYTHING:
124
+ - SSR component loading ✅ (correct - Node.js can load SSR files)
125
+ - Client directory injection ❌ (wrong - browser gets SSR path)
126
+ - Static file serving ✅ (Mount 4 serves SSR files at `/.app/ssr/components`)
127
+ 3. **Browser** receives: `window.__COMPONENT_DIR__ = ".app/ssr/components"`
128
+ 4. **Browser** requests: `/.app/ssr/components/layout/root.js`
129
+ 5. **Express** serves SSR component (via Mount 4) ✅
130
+ 6. **Browser** executes SSR JavaScript:
131
+ - Contains bare imports: `import { onMount } from 'svelte'`
132
+ - Contains chunk imports: `await import("../../chunks/liveWebsocket-BL1FcyHq.js")`
133
+ - Resolves to: `.app/ssr/chunks/liveWebsocket-BL1FcyHq.js` ❌ (doesn't exist)
134
+ 7. **Result:** Module not found → `effect_orphan` error
135
+
136
+ ### Why SSR Components Fail in Browser
137
+ SSR components are compiled for Node.js:
138
+ - Use bare module imports (`'svelte'`, `'svelte/server'`)
139
+ - Expect Node.js module resolution
140
+ - Cannot run in browser environment
141
+
142
+ Client components are Vite-bundled for browsers:
143
+ - All imports are resolved and bundled
144
+ - Chunks are properly linked
145
+ - Can execute in browser
146
+
147
+ ---
148
+
149
+ ## What We're GUESSING / Hypothesizing
150
+
151
+ ### Hypothesis 1: We're Passing the Wrong Path
152
+ **Theory:** We should pass `component_dir_client` to Forge instead of `component_dir_ssr`.
153
+
154
+ **Problem:** If we pass `/assets/components`:
155
+ - Browser would load client components ✅ (correct)
156
+ - But SSR would try to load from `/dist/assets/components/` ❌
157
+ - SSR files are actually at `/dist/.app/ssr/components/`
158
+ - SSR would break
159
+
160
+ **Conclusion:** Forge's single-path design doesn't support separate SSR/client directories.
161
+
162
+ ### Hypothesis 2: Build Output Should Match Forge's Expectations
163
+ **Theory:** Maybe we should output components to a single directory that works for both SSR and client.
164
+
165
+ **Problem:** SSR and client components are fundamentally different:
166
+ - SSR: ES modules with bare imports (for Node.js)
167
+ - Client: Vite-bundled with resolved imports (for browser)
168
+ - They MUST be separate files
169
+
170
+ **Conclusion:** We need two directories, not one.
171
+
172
+ ### Hypothesis 3: Forge Needs to Support Separate Paths
173
+ **Theory:** Modify Forge to accept `component_dir_ssr` and `component_dir_client`.
174
+
175
+ **Changes needed:**
176
+ 1. `ServerOptions.ts`: Add new optional fields
177
+ 2. `server.ts`: Calculate both paths
178
+ 3. `express_server_adapter.ts`: Use client path for `window.__COMPONENT_DIR__`
179
+ 4. `ProdComponentLoader`: Use SSR path for server-side loading
180
+
181
+ **Status:** This seems like the correct solution, but requires Forge modifications.
182
+
183
+ ### Hypothesis 4: Pass Relative Path "components" to Forge
184
+ **Theory:** The original hammer.config.yml specifies `componentDir: frontend/components`. Relative to `frontend/frontend.ts`, this is just `components`. Maybe we should pass just `"components"` to Forge?
185
+
186
+ **Analysis:**
187
+ - Forge would calculate: `COMPONENT_DIR = /dist/components`
188
+ - Browser would request: `/components/layout/root.js`
189
+ - But files don't exist at `/dist/components/` ❌
190
+ - Files are at `/dist/.app/assets/components/` and `/dist/.app/ssr/components/`
191
+
192
+ **Conclusion:** This doesn't match our current build structure.
193
+
194
+ ### Hypothesis 5: Change Build Structure
195
+ **Theory:** Output components to `/dist/components/` (single location) instead of separate SSR/client dirs.
196
+
197
+ **Problems:**
198
+ - How would we differentiate SSR vs client builds?
199
+ - Vite outputs to one location, SSR to another
200
+ - Would require major build pipeline changes
201
+ - Unclear if this is even possible with current tools
202
+
203
+ **Status:** Needs more investigation into Vite's capabilities.
204
+
205
+ ---
206
+
207
+ ## Original Config Intent
208
+
209
+ **From `hammer.config.yml`:**
210
+ ```yaml
211
+ client:
212
+ main: frontend/frontend.ts # UI root: /project/frontend/
213
+ componentDir: frontend/components # Components: /project/frontend/components/
214
+ ```
215
+
216
+ **Relative calculation:**
217
+ - UI root: `frontend/`
218
+ - Component dir: `frontend/components/`
219
+ - Relative path: `components/` (just the subdirectory)
220
+
221
+ **Bootstrap calculates:**
222
+ - `component_dir_ssr`: `.app/ssr/components`
223
+ - `component_dir_client`: `/assets/components`
224
+
225
+ **The question:** Should Forge receive:
226
+ - Option A: `.app/ssr/components` (full SSR path, current behavior)
227
+ - Option B: `/assets/components` (full client path, breaks SSR)
228
+ - Option C: `components` (relative path, no files at `/dist/components/`)
229
+ - Option D: Both paths via new Forge API
230
+
231
+ ---
232
+
233
+ ## Potential Solutions
234
+
235
+ ### Solution 1: Modify Forge (Most Correct)
236
+ **Pros:**
237
+ - Clean separation of concerns
238
+ - SSR and client use correct directories
239
+ - No breaking changes to build pipeline
240
+
241
+ **Cons:**
242
+ - Requires Forge modifications
243
+ - Need to update Forge across all projects
244
+
245
+ **Files to change:**
246
+ 1. `forge/src/options/ServerOptions.ts` - Add `component_dir_ssr` and `component_dir_client`
247
+ 2. `forge/src/server/server.ts` - Calculate both paths
248
+ 3. `forge/src/routing/server_adapter/express_server_adapter.ts` - Use client path for injection
249
+ 4. `hammer/src/client.js` - Pass both paths
250
+
251
+ ### Solution 2: Change Hammer Build to Output Single Component Directory
252
+ **Approach:** Build both SSR and client to same location, differentiate by filename suffix.
253
+
254
+ **Example:**
255
+ ```
256
+ dist/components/
257
+ ├── layout/
258
+ │ ├── root.ssr.js # For server-side rendering
259
+ │ └── root.js # For browser
260
+ ```
261
+
262
+ **Pros:**
263
+ - Works with Forge's current API
264
+ - No Forge modifications needed
265
+
266
+ **Cons:**
267
+ - Major build pipeline changes
268
+ - Unclear if Vite supports this pattern
269
+ - May break component loader expectations
270
+ - High risk of regressions
271
+
272
+ ### Solution 3: Pass Client Path to Forge, Handle SSR Separately
273
+ **Approach:** Pass `/assets/components` to Forge, manually load SSR components elsewhere.
274
+
275
+ **Problems:**
276
+ - How would SSR loading work?
277
+ - Would need to bypass Forge's component loader
278
+ - Unclear if this is even possible
279
+ - High complexity, fragile
280
+
281
+ ### Solution 4: Mount Client Components at SSR Path
282
+ **Approach:** Add another Express mount that serves client components at `/.app/ssr/components`.
283
+
284
+ **Example:**
285
+ ```javascript
286
+ app.use('/.app/ssr/components', express.static('/dist/.app/assets/components'))
287
+ ```
288
+
289
+ **Pros:**
290
+ - No code changes to Forge
291
+ - Quick fix
292
+
293
+ **Cons:**
294
+ - Conceptually wrong (SSR path serves client files)
295
+ - Confusing for developers
296
+ - SSR would still try to load from wrong location
297
+ - Doesn't fix the root problem
298
+
299
+ ---
300
+
301
+ ## Open Questions
302
+
303
+ 1. **Can Vite output to a single component directory?**
304
+ - Can we build SSR and client to same location with different naming?
305
+ - Does this conflict with Vite's chunk optimization?
306
+
307
+ 2. **Does Forge's dev mode work correctly?**
308
+ - Dev mode uses Vite dev server
309
+ - Does it have the same SSR/client split issue?
310
+ - Line 264: `clientComponentDir = this.isProd ? this.componentDir : '/assets'`
311
+ - In dev: Always uses `/assets` - why does this work?
312
+
313
+ 3. **Is the `.app/` directory structure mandatory?**
314
+ - Could we output to `/dist/ssr/` and `/dist/assets/` instead?
315
+ - Would this simplify path calculations?
316
+
317
+ 4. **What did the original Forge design expect?**
318
+ - Was Forge designed for projects with separate SSR/client builds?
319
+ - Or was it designed for unified component directories?
320
+
321
+ 5. **Are there other projects using this stack successfully?**
322
+ - How do they structure their builds?
323
+ - Can we learn from existing patterns?
324
+
325
+ ---
326
+
327
+ ## Next Steps
328
+
329
+ 1. **Decide on solution approach:**
330
+ - Option 1: Modify Forge (cleanest, most correct)
331
+ - Option 2: Restructure build output (risky, high effort)
332
+ - Option 3: Quick mount hack (temporary workaround)
333
+
334
+ 2. **If Option 1 (Modify Forge):**
335
+ - Design the new API surface
336
+ - Write tests for dual-path support
337
+ - Implement changes in Forge
338
+ - Update hammer to use new API
339
+ - Test with markdown_view, liftlog, noblelaw
340
+
341
+ 3. **If Option 2 (Restructure Build):**
342
+ - Research Vite multi-output capabilities
343
+ - Design new directory structure
344
+ - Update build pipeline
345
+ - Test thoroughly across all projects
346
+
347
+ 4. **Validate the fix:**
348
+ - Run all 18 unit tests
349
+ - Test markdown_view in production mode
350
+ - Verify no `effect_orphan` errors
351
+ - Confirm browser loads correct (client) components
352
+ - Verify SSR still works
353
+
354
+ ---
355
+
356
+ ## Timeline Estimate
357
+
358
+ - **Option 1 (Modify Forge):** 2-3 hours implementation + 1 hour testing
359
+ - **Option 2 (Restructure Build):** 1-2 days research + 2-3 days implementation + 2 hours testing
360
+ - **Option 3 (Quick Hack):** 30 minutes, but doesn't solve root cause
361
+
362
+ ---
363
+
364
+ ## Files Reference
365
+
366
+ ### Confirmed File Locations
367
+ - `/Users/shavauhngabay/dev/hammer/src/client.js` - Passes component_dir to Forge
368
+ - `/Users/shavauhngabay/dev/hammer/src/runtime/runtime.js` - Express static mounts
369
+ - `/Users/shavauhngabay/dev/hammer/src/build/bootstrap.js` - Calculates component paths
370
+ - `/Users/shavauhngabay/dev/forge/src/server/server.ts` - Forge server initialization
371
+ - `/Users/shavauhngabay/dev/forge/src/options/ServerOptions.ts` - Forge options interface
372
+ - `/Users/shavauhngabay/dev/forge/src/routing/server_adapter/express_server_adapter.ts` - Injects window.__COMPONENT_DIR__
373
+ - `/Users/shavauhngabay/dev/forge/src/routing/component_loader/component_loader.ts` - ProdComponentLoader
374
+ - `/Users/shavauhngabay/dev/markdown_view/hammer.config.yml` - Project configuration
375
+ - `/Users/shavauhngabay/dev/markdown_view/dist/no_ego.js` - Generated runtime config
376
+
377
+ ---
378
+
379
+ **END OF DOCUMENT**
380
+
381
+ For questions or to proceed with implementation, contact the engineering team.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noego/app",
3
- "version": "0.0.7",
3
+ "version": "0.0.10",
4
4
  "description": "Production build tool for Dinner/Forge apps.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,6 +30,8 @@
30
30
  "author": "App Build CLI",
31
31
  "dependencies": {
32
32
  "deepmerge": "^4.3.1",
33
+ "glob-parent": "^6.0.2",
34
+ "http-proxy": "^1.18.1",
33
35
  "picomatch": "^2.3.1",
34
36
  "yaml": "^2.6.0"
35
37
  },
@@ -38,5 +40,7 @@
38
40
  "@noego/forge": "*",
39
41
  "express": "*"
40
42
  },
41
- "devDependencies": {}
43
+ "devDependencies": {
44
+ "chokidar": "^4.0.3"
45
+ }
42
46
  }