@bluestep-systems/bspecs 0.10.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 (83) hide show
  1. package/README.md +129 -0
  2. package/cli.js +74 -0
  3. package/package.json +30 -0
  4. package/src/prompts.js +74 -0
  5. package/src/scaffold.js +152 -0
  6. package/src/sync.js +123 -0
  7. package/src/utils.js +95 -0
  8. package/templates/claude/agents/b6p-code-review.md +81 -0
  9. package/templates/claude/agents/b6p-commenter.md +59 -0
  10. package/templates/claude/agents/b6p-task-implementer.md +77 -0
  11. package/templates/claude/hooks/block-generated-files.sh +16 -0
  12. package/templates/claude/hooks/block-tsc.sh +16 -0
  13. package/templates/claude/hooks/prettier-on-save.sh +21 -0
  14. package/templates/claude/instructions/b6p-platform.md.template +185 -0
  15. package/templates/claude/instructions/bsjs-development.md.template +430 -0
  16. package/templates/claude/instructions/conventions/always-snapshot.md.template +25 -0
  17. package/templates/claude/instructions/conventions/blueiq-no-ai-branding.md.template +11 -0
  18. package/templates/claude/instructions/conventions/date-format.md.template +27 -0
  19. package/templates/claude/instructions/conventions/endpoint-approach.md.template +9 -0
  20. package/templates/claude/instructions/conventions/formula-patterns.md.template +71 -0
  21. package/templates/claude/instructions/conventions/no-global-dollar.md.template +9 -0
  22. package/templates/claude/instructions/conventions/push-inner-draft.md.template +21 -0
  23. package/templates/claude/instructions/conventions/separate-files.md.template +17 -0
  24. package/templates/claude/instructions/conventions/single-script.md.template +28 -0
  25. package/templates/claude/instructions/conventions/snapshot-integrity.md.template +23 -0
  26. package/templates/claude/instructions/conventions/top-level-const-tdz.md.template +33 -0
  27. package/templates/claude/instructions/conventions/ts-in-template-literal.md.template +48 -0
  28. package/templates/claude/instructions/conventions/tsc-rootdir.md.template +17 -0
  29. package/templates/claude/instructions/gotchas/common-gotchas.md.template +91 -0
  30. package/templates/claude/instructions/gotchas/fetched-resource-code.md.template +9 -0
  31. package/templates/claude/instructions/index.md.template +82 -0
  32. package/templates/claude/instructions/reference/api-patterns.md.template +487 -0
  33. package/templates/claude/instructions/reference/blueiq-credit-integration-playbook.md.template +31 -0
  34. package/templates/claude/instructions/reference/chronounit-months.md.template +37 -0
  35. package/templates/claude/instructions/reference/code-patterns.md.template +265 -0
  36. package/templates/claude/instructions/reference/component-library.md.template +217 -0
  37. package/templates/claude/instructions/reference/crm-dashboard-inspo.md.template +17 -0
  38. package/templates/claude/instructions/reference/csv-parsing.md.template +18 -0
  39. package/templates/claude/instructions/reference/dashboard-design-system.md.template +38 -0
  40. package/templates/claude/instructions/reference/datetime-field-write.md.template +27 -0
  41. package/templates/claude/instructions/reference/design-system.md.template +150 -0
  42. package/templates/claude/instructions/reference/dpn-dashboard-framework.md.template +29 -0
  43. package/templates/claude/instructions/reference/endpoint-method-call.md.template +10 -0
  44. package/templates/claude/instructions/reference/endpoint-no-delete-method.md.template +9 -0
  45. package/templates/claude/instructions/reference/endpoint-output-channel.md.template +23 -0
  46. package/templates/claude/instructions/reference/endpoint-urls.md.template +15 -0
  47. package/templates/claude/instructions/reference/entry-delete.md.template +40 -0
  48. package/templates/claude/instructions/reference/file-execution.md.template +113 -0
  49. package/templates/claude/instructions/reference/http-requester.md.template +37 -0
  50. package/templates/claude/instructions/reference/id-full-vs-short.md.template +15 -0
  51. package/templates/claude/instructions/reference/internal-loopback-fetch.md.template +24 -0
  52. package/templates/claude/instructions/reference/localdate-parse.md.template +16 -0
  53. package/templates/claude/instructions/reference/merge-report-memo-json.md.template +25 -0
  54. package/templates/claude/instructions/reference/merge-report-static-index.md.template +29 -0
  55. package/templates/claude/instructions/reference/merge-report-urls.md.template +67 -0
  56. package/templates/claude/instructions/reference/multi-entry-in-multi-entry.md.template +21 -0
  57. package/templates/claude/instructions/reference/named-controls-submit.md.template +11 -0
  58. package/templates/claude/instructions/reference/new-entry-id.md.template +30 -0
  59. package/templates/claude/instructions/reference/relationship-field-set.md.template +37 -0
  60. package/templates/claude/instructions/reference/send-message-abort.md.template +37 -0
  61. package/templates/claude/instructions/reference/session-cookie-forwarding.md.template +31 -0
  62. package/templates/claude/instructions/reference/singleselect-null-copy.md.template +21 -0
  63. package/templates/claude/instructions/reference/staff-query-permission-gating.md.template +27 -0
  64. package/templates/claude/instructions/reference/timefield-vs-datetimefield.md.template +13 -0
  65. package/templates/claude/instructions/reference/user-zone-id.md.template +16 -0
  66. package/templates/claude/settings.json.template +46 -0
  67. package/templates/claude/skills/b6p-audit/SKILL.md +82 -0
  68. package/templates/claude/skills/b6p-pull/SKILL.md +123 -0
  69. package/templates/claude/skills/b6p-push/SKILL.md +70 -0
  70. package/templates/claude/skills/bug-fix/SKILL.md +28 -0
  71. package/templates/claude/skills/spec-create/SKILL.md +60 -0
  72. package/templates/claude/skills/spec-execute/SKILL.md +51 -0
  73. package/templates/claude/skills/spec-status/SKILL.md +20 -0
  74. package/templates/claude/skills/task-comment/SKILL.md +96 -0
  75. package/templates/claude/spec-templates/design.template.md +36 -0
  76. package/templates/claude/spec-templates/requirements.template.md +26 -0
  77. package/templates/claude/spec-templates/tasks.template.md +37 -0
  78. package/templates/module/README.md.template +46 -0
  79. package/templates/root/.gitignore.template +14 -0
  80. package/templates/root/.prettierrc.template +8 -0
  81. package/templates/root/CLAUDE.md.template +157 -0
  82. package/templates/root/README.md.template +58 -0
  83. package/templates/root/package.json.template +15 -0
@@ -0,0 +1,430 @@
1
+ ---
2
+ description: Deep technical reference for BsJs (BlueStep TypeScript). Read when writing or modifying component code.
3
+ applyTo: "**/draft/scripts/**/*.ts"
4
+ ---
5
+
6
+ # BsJs Development Reference
7
+
8
+ Deep reference for BlueStep TypeScript development. Critical rules live in `CLAUDE.md`; this file covers patterns, APIs, and conventions that apply when actually writing code.
9
+
10
+ ## Contents
11
+
12
+ - [Module structure](#module-structure)
13
+ - [The `B` object — full API](#the-b-object--full-api)
14
+ - [Reading and writing fields](#reading-and-writing-fields)
15
+ - [Patterns by script type](#patterns-by-script-type)
16
+ - [`info/` configuration](#info-configuration)
17
+ - [TypeScript configuration & Graal compatibility](#typescript-configuration--graal-compatibility)
18
+ - [Imports — never fabricate](#imports--never-fabricate)
19
+ - [TS narrowing pitfalls (Graal/Java types)](#ts-narrowing-pitfalls-graaljava-types)
20
+ - [Error handling](#error-handling)
21
+
22
+ ## Module structure
23
+
24
+ ```
25
+ <project-root>/
26
+ └── U######/ ← Unit folder, created by `b6p pull`
27
+ └── ComponentName/
28
+ ├── declarations/ ← platform-generated, DO NOT EDIT
29
+ │ ├── B.d.ts
30
+ │ ├── scriptlibrary.d.ts
31
+ │ ├── Globals.d.ts
32
+ │ └── index.d.ts ← platform-generated field/query/form declarations
33
+ └── draft/
34
+ ├── scripts/ ← TypeScript source
35
+ │ ├── app.ts ← entry point
36
+ │ └── <feature>.ts ← split modules
37
+ ├── objects/
38
+ │ └── imports.ts ← legacy artifact in older modules; not updated on pull
39
+ ├── static/ ← MergeReport only: HTML, CSS, client JS
40
+ └── info/
41
+ ├── config.json
42
+ ├── metadata.json ← identifies component type (read to know what this is)
43
+ └── permissions.json
44
+ ```
45
+
46
+ A project may contain multiple Unit folders, each with multiple components of varying types. **Module split convention:** split complex logic into focused files under `scripts/`. `app.ts` is the entry point.
47
+
48
+ **Multi-file components with ES imports are supported** (verified on the platform — `SMS Data Diagnostics`, `app.ts` importing `cleanupDuplicates.ts`). A sibling file `export`s a symbol and `app.ts` pulls it in with a standard relative ES import (no file extension); it compiles and links correctly after push. Use this to split a large `app.ts` into focused modules:
49
+
50
+ ```typescript
51
+ // scripts/cleanupDuplicates.ts
52
+ export function cleanupDuplicates(): void { /* ... */ }
53
+
54
+ // scripts/app.ts
55
+ import { cleanupDuplicates } from "./cleanupDuplicates";
56
+ ```
57
+
58
+ ## The `B` object — full API
59
+
60
+ ### `B.net` — outbound HTTP
61
+
62
+ ```typescript
63
+ const response = B.net.fetch("https://api.example.com/data", {
64
+ method: "POST",
65
+ headers: { "Content-Type": "application/json" },
66
+ body: JSON.stringify({ key: "value" }),
67
+ timeout: 30000,
68
+ });
69
+
70
+ if (response.ok) {
71
+ const data = JSON.parse(response.body);
72
+ }
73
+ ```
74
+
75
+ ### `B.time` — date/time
76
+
77
+ Use `B.time` for any date/time work. Native `Date` is not supported.
78
+
79
+ ```typescript
80
+ const now = B.time.now();
81
+ const formatted = B.time.format(now, "yyyy-MM-dd HH:mm:ss");
82
+ const parsed = B.time.parse("2026-05-15", "yyyy-MM-dd");
83
+ ```
84
+
85
+ ### `B.queries` — query objects
86
+
87
+ Queries are defined on the platform and exposed in `declarations/index.d.ts` (platform-generated). Reference them only after pulling.
88
+
89
+ ```typescript
90
+ const results = B.queries.activeClients.execute();
91
+ for (const record of results) {
92
+ // ...
93
+ }
94
+ ```
95
+
96
+ **Unit scoping:** queries are unit-scoped by default. When re-using a query across units, call `clearSearchAndSort()` to reset filters:
97
+
98
+ ```typescript
99
+ const query = B.queries.allRecords;
100
+ query.execute(); // current unit
101
+ query.clearSearchAndSort();
102
+ query.unit = otherUnit;
103
+ query.execute(); // other unit
104
+ ```
105
+
106
+ ### `B.exports` — cross-formula data
107
+
108
+ Used to pass data between formulas in the same execution context.
109
+
110
+ ```typescript
111
+ B.exports.totalScore = computeScore();
112
+ B.clearExports(); // when starting fresh
113
+ ```
114
+
115
+ ### `B.user` — current user
116
+
117
+ `B.user` is **null** in scheduled / cron scripts. Always guard:
118
+
119
+ ```typescript
120
+ if (B.user) {
121
+ const userId = B.user.id;
122
+ }
123
+ ```
124
+
125
+ ### `B.commit()` — manual transaction commit
126
+
127
+ The platform calls `B.commit()` automatically when the script finishes. Only call it manually when:
128
+
129
+ - You need a newly created entry's ID before the script ends.
130
+ - You need post-saves to fire before subsequent reads.
131
+
132
+ ```typescript
133
+ const newEntry = B.queries.someForm.getNewEntry();
134
+ newEntry.name.set("test");
135
+ B.commit();
136
+ const id = newEntry.id; // now available
137
+ ```
138
+
139
+ ## Reading and writing fields
140
+
141
+ ### Read
142
+
143
+ ```typescript
144
+ const value = entry.fieldName.val(); // current value
145
+ const exportValue = entry.statusField.selectedExportValue(); // dropdown export value
146
+ const all = entry.tagsField.allValues(); // multi-select
147
+ ```
148
+
149
+ `.val()` returns the field's value typed loosely (often `any`). When a field may be empty, prefer the Java-optional accessor with a default instead of null-checking the raw value:
150
+
151
+ ```typescript
152
+ const name = entry.nameField.opt().orElse(""); // string, never null
153
+ const score = entry.scoreField.opt().orElse(0);
154
+ ```
155
+
156
+ Because projects compile with `strict: false` (see "TypeScript configuration & Graal compatibility"), `.opt().orElse()` plus explicit annotations are the main defense against silent `null`/`undefined` bugs.
157
+
158
+ ### Write
159
+
160
+ Do not call `.writable()`. Field writability is configured on the platform — if the field is not configured as writable for this script type, the write will throw at runtime. Write directly:
161
+
162
+ ```typescript
163
+ entry.nameField.set("new value");
164
+ entry.statusField.setByExportValue("active");
165
+ entry.tagsField.add("priority");
166
+ entry.tagsField.remove("legacy");
167
+ ```
168
+
169
+ ### Multi-entry forms
170
+
171
+ ```typescript
172
+ for (const entry of record.someForm.allEntries) {
173
+ entry.field.set("value");
174
+ }
175
+
176
+ const newEntry = record.someForm.getNewEntry();
177
+ newEntry.name.set("new");
178
+
179
+ oldEntry.delete();
180
+ ```
181
+
182
+ ## Patterns by script type
183
+
184
+ ### Post-Save
185
+
186
+ Runs after a record is saved. Use `justCreated()` to distinguish new vs. updated records. `curEntry` is the entry being saved.
187
+
188
+ ```typescript
189
+ export function run(): void {
190
+ if (curEntry.justCreated()) {
191
+ sendWelcomeEmail();
192
+ } else {
193
+ syncToExternal();
194
+ }
195
+ }
196
+ ```
197
+
198
+ ### Endpoint
199
+
200
+ Receives an HTTP request, returns a response. **Set `contentType` before writing the body. Use exactly one output method per request** (`out`, `stream`, or `redirect`).
201
+
202
+ ```typescript
203
+ export function run(): void {
204
+ const action = request.param("action");
205
+ switch (action) {
206
+ case "list": return listAll();
207
+ case "get": return getOne();
208
+ default: return badRequest();
209
+ }
210
+ }
211
+
212
+ function listAll(): void {
213
+ response.contentType = "application/json";
214
+ response.out(JSON.stringify({ items: getItems() }));
215
+ }
216
+
217
+ function badRequest(): void {
218
+ response.status = 400;
219
+ response.contentType = "text/plain";
220
+ response.out("Unknown action");
221
+ }
222
+ ```
223
+
224
+ #### Streaming (large responses)
225
+
226
+ NDJSON pattern for streaming large datasets:
227
+
228
+ ```typescript
229
+ response.contentType = "application/x-ndjson";
230
+ const stream = response.stream();
231
+ for (const record of B.queries.largeQuery.execute()) {
232
+ stream.write(JSON.stringify({ id: record.id, name: record.name.val() }) + "\n");
233
+ }
234
+ stream.close();
235
+ ```
236
+
237
+ #### Redirect
238
+
239
+ ```typescript
240
+ response.redirect("/some/path");
241
+ ```
242
+
243
+ ### MergeReport
244
+
245
+ Backend logic in `scripts/`; **frontend in `static/`** (R3). Inject content into pages via `pageContent()`:
246
+
247
+ ```typescript
248
+ export function run(): void {
249
+ pageContent("main").write(renderMain());
250
+ HEADER_SCRIPTS_BOTTOM().write(`<script src="${staticUrl("client.js")}"></script>`);
251
+ BODY().write(`<div class="footer">...</div>`);
252
+ }
253
+ ```
254
+
255
+ ### OnDemand / Field Formula
256
+
257
+ OnDemand formulas run on the **task pod** — well-suited for heavy or long-running work because they keep load off production pods. Trigger them asynchronously and let the result land in a record the caller can poll or react to.
258
+
259
+ **Critical latency caveat:** OnDemand has a ~5 second scheduler-queue delay before it starts, by design. This is fine for background / async work. It is the wrong tool for anything on a user's synchronous wait path — if a user is waiting for a response, an OnDemand hop adds ~5 s of irreducible latency before the work even begins. Prefer a synchronous task-pod **Endpoint** for user-facing create or update paths.
260
+
261
+ ```typescript
262
+ export function run(): void {
263
+ const result = runFormula();
264
+ B.message.info(`Result: ${result}`);
265
+ }
266
+ ```
267
+
268
+ ### Scheduled / cron
269
+
270
+ `B.user` is null. Use stored credentials or hardcoded service identities, and guard accordingly.
271
+
272
+ ```typescript
273
+ export function run(): void {
274
+ if (B.user) {
275
+ log.warn("Scheduled script should not have a user context");
276
+ return;
277
+ }
278
+ doScheduledWork();
279
+ }
280
+ ```
281
+
282
+ ### WebSocket push
283
+
284
+ ```typescript
285
+ B.io.sendMessage(channelId, JSON.stringify({ type: "update", payload: data }));
286
+ ```
287
+
288
+ ## `info/` configuration
289
+
290
+ ### `config.json` (required)
291
+
292
+ ```json
293
+ {
294
+ "language": "BsJs",
295
+ "entryPoint": "scripts/app.ts"
296
+ }
297
+ ```
298
+
299
+ ### `metadata.json`
300
+
301
+ Identifies the component type. Examples:
302
+
303
+ - `"triggerType": "POST_SAVE"` — post-save
304
+ - `"triggerType": "ENDPOINT"` — endpoint
305
+ - `"triggerType": "ON_DEMAND"` — on-demand
306
+ - `"triggerType": "SCHEDULED"` — scheduled
307
+
308
+ ### `permissions.json`
309
+
310
+ Defines who can execute / view this component. Managed on the platform; usually pulled, rarely hand-edited.
311
+
312
+ ## TypeScript configuration & Graal compatibility
313
+
314
+ ### tsconfig and strict mode
315
+
316
+ Each project root has a `tsconfig.json`. BlueStep projects run with **`strict: false`**, so the compiler will *not* catch every null/type error — compensate with `.opt().orElse()` and explicit type annotations. Typical settings:
317
+
318
+ ```json
319
+ {
320
+ "compilerOptions": {
321
+ "target": "ES2020",
322
+ "module": "ESNext",
323
+ "strict": false,
324
+ "moduleResolution": "node",
325
+ "types": ["./declarations"]
326
+ }
327
+ }
328
+ ```
329
+
330
+ Do not run `tsc` locally — the platform compiles on push (a hook blocks local `tsc`). For why local builds / `rootDir` are avoided, see `conventions/tsc-rootdir.md`.
331
+
332
+ ### Graal compatibility (server-side)
333
+
334
+ Server-side BsJs runs on **GraalVM**, not Node. Avoid relying on the newest language features there:
335
+
336
+ - Stage-3 / very new ECMAScript proposals
337
+ - Newer `Intl` APIs
338
+ - `WeakRef` and `FinalizationRegistry` (limited support)
339
+
340
+ Server-side `B.net.fetch` is **synchronous** — no `await`/Promise round-trip (see "`B.net` — outbound HTTP"). The `async`/`await` and browser `fetch().json()` patterns apply only to client JS shipped in a MergeReport's `static/`, which runs in the browser under normal compatibility rules.
341
+
342
+ ## Imports — never fabricate
343
+
344
+ Query, form, and field references must exist in **the component you are editing**'s `declarations/index.d.ts` **before** you reference them in code. Form-field imports are per-component — another component's declarations file tells you nothing about this one. If a name is missing:
345
+
346
+ 1. Add it to **this component's** form-import config on the platform.
347
+ 2. Run `npx b6p pull "<DAV URL>"` to update this component's declarations.
348
+ 3. Then reference it in TypeScript.
349
+
350
+ Hallucinating an import name silently passes type-check locally if the file is missing, but will fail at platform compile.
351
+
352
+ ## TS narrowing pitfalls (Graal/Java types)
353
+
354
+ Some TypeScript patterns fail silently with the Java types exposed by `B` (especially `Java.Time.Instant`, `Java.Time.ZonedDateTime`, and any `Optional`-like interface). Avoiding them upfront saves edit → diagnostic → fix cycles.
355
+
356
+ ### Narrowing broken in closures
357
+
358
+ **Anti-pattern:**
359
+
360
+ ```typescript
361
+ let latest: Java.Time.Instant | null = null;
362
+ collection.forEach((item) => {
363
+ const inst = item.getInstant();
364
+ if (!latest || inst.isAfter(latest)) { // ❌ TS narrowing collapses to `never`
365
+ latest = inst;
366
+ }
367
+ });
368
+ ```
369
+
370
+ TypeScript cannot guarantee that the captured variable is still non-null when the next line of the callback executes, so it collapses the type to `never` and `.isAfter()` stops existing.
371
+
372
+ **Solutions, in order of preference:**
373
+
374
+ 1. **Compare primitive values** (best): avoid the nullable union entirely by using epoch millis, ISO strings, or a sentinel value.
375
+
376
+ ```typescript
377
+ let latestMs = -1;
378
+ collection.forEach((item) => {
379
+ const ms = item.getInstant().toEpochMilli();
380
+ if (ms > latestMs) latestMs = ms;
381
+ });
382
+ ```
383
+
384
+ 2. **Local alias with explicit type** (when the Java type must be kept): capture the variable in a `const` with the declared type before the comparison, forcing TS to re-narrow within the local scope.
385
+
386
+ ```typescript
387
+ let latest: Java.Time.Instant | null = null;
388
+ collection.forEach((item) => {
389
+ const inst = item.getInstant();
390
+ const prev: Java.Time.Instant | null = latest; // explicit alias
391
+ if (prev === null || inst.isAfter(prev)) {
392
+ latest = inst;
393
+ }
394
+ });
395
+ ```
396
+
397
+ 3. **Accumulate then reduce**: when the above feels forced, `forEach` into an array and sort/reduce outside the loop.
398
+
399
+ ### Symptoms to recognize
400
+
401
+ - `Property '<x>' does not exist on type 'never'.` inside a callback that captures a nullable `let`: 99% of the time this is this pitfall.
402
+ - "Works the first time but breaks after refactoring to multi-step": suspect broken narrowing.
403
+
404
+ ## Error handling
405
+
406
+ The platform surfaces uncaught exceptions in the script log. Patterns:
407
+
408
+ ```typescript
409
+ try {
410
+ const response = B.net.fetch(url, { timeout: 5000 });
411
+ if (!response.ok) {
412
+ log.error(`Upstream returned ${response.status}`);
413
+ return;
414
+ }
415
+ process(response.body);
416
+ } catch (err) {
417
+ log.error(`Fetch failed: ${err.message}`);
418
+ }
419
+ ```
420
+
421
+ For endpoints, prefer explicit status codes over throwing:
422
+
423
+ ```typescript
424
+ if (!validInput) {
425
+ response.status = 400;
426
+ response.contentType = "application/json";
427
+ response.out(JSON.stringify({ error: "invalid input" }));
428
+ return;
429
+ }
430
+ ```
@@ -0,0 +1,25 @@
1
+ ---
2
+ description: "BlueStep pull/push/snapshot workflow — always use the CLI scripts (never fetch manually) and always snapshot after every change via push → pull → snapshot"
3
+ ---
4
+
5
+ Always use the BlueStep CLI scripts. Never use WebFetch, curl, or the Write tool to interact with BlueStep files manually.
6
+
7
+ - Pull: `node ~/.bluestep/pull.js <url>`
8
+ - Push: `node ~/.bluestep/push.js [folder]`
9
+ - Snapshot: `node ~/.bluestep/push.js --snapshot --comment "message" [folder]`
10
+
11
+ **Why:** A full system handles auth, WebDAV, folder naming by display name, `.b6p_url.json` tracking, and GraphQL snapshot-history recording. Bypassing it breaks the whole workflow.
12
+
13
+ **How to apply:** Any time the user says "pull", "push", or "snapshot" in a BlueStep context, use the CLI scripts. Credentials are in `~/.bluestep/config.json` — never prompt for them. Always snapshot after every code change — do not wait to be asked. Always include a `--comment` with a 1–3 sentence summary of what changed and why.
14
+
15
+ ## Snapshot workflow — always push → pull → snapshot
16
+
17
+ Never run `--snapshot` directly after editing. The correct sequence is:
18
+
19
+ 1. `node ~/.bluestep/push.js [folder]` — push source files to draft (BlueStep compiles TypeScript server-side).
20
+ 2. `node ~/.bluestep/pull.js <url>` — pull to get the freshly compiled `.build/` files from the server.
21
+ 3. `node ~/.bluestep/push.js --snapshot --comment "summary of changes" [folder]` — now the snapshot has the correct compiled JS and records history.
22
+
23
+ **Why:** `push.js` skips `.build/` directories (compiled artifacts), and BlueStep compiles TypeScript on the server. Snapshotting without pulling first captures stale local `.build/` files and reverts the compiled output to an older version. The pull step fetches the server-compiled JS before snapshotting. The `--comment` flag records a GraphQL history entry (author, timestamp, message) matching what the VS Code extension does.
24
+
25
+ Related: [snapshot integrity](snapshot-integrity.md) (including locally-compiled `.build/*` in the snapshot `saveState`), [push inner draft](push-inner-draft.md), [tsc rootdir](tsc-rootdir.md).
@@ -0,0 +1,11 @@
1
+ ---
2
+ description: "On BlueStep BlueIQ work, never use \"AI\" in front-facing/user-visible text — the brand is always \"BlueIQ\""
3
+ ---
4
+
5
+ On the BlueStep BlueIQ stack (summitridge: `/b/aiAudio` 1471299, `/b/aiCredits` 1471659, Shift Note Assistant 1471399, AI Audio Tester 1471304), never use the word "AI" in any front-facing / user-visible text. The user-facing brand is always **BlueIQ**. E.g. "out of monthly credits" (not "AI credits"), "BlueIQ Audio endpoint alive" (not "AI Audio"), "BlueIQ Assistant" (not "AI Audio Tester").
6
+
7
+ **Why:** BlueIQ is the brand name customers see; AI is not the front-facing branding for this product line.
8
+
9
+ **How to apply:** Scrub "AI" only from user-visible strings: HTML/UI text, button/panel labels, status messages, and JSON `error`/`status`/`message` fields that surface to a user. Leave as-is (exempt): the provider's real name "OpenAI"; internal identifiers and URL paths (`/b/aiCredits`, `/b/aiAudio`, `AI_CREDITS_PATH`, `openAIIntegration`, `openAILogs`, `aiaud-*` CSS classes); and code comments. Admin-only ledger "function" labels were also rebranded to "BlueIQ …" for consistency, but they are lower priority.
10
+
11
+ Related: [blueiq credit integration playbook](../reference/blueiq-credit-integration-playbook.md).
@@ -0,0 +1,27 @@
1
+ ---
2
+ description: "BlueStep addSearch needs MM/DD/YYYY for date fields — YYYY/MM/DD passes validation but silently fails to filter; raw Java LocalDate objects also fail"
3
+ ---
4
+
5
+ When building MEF queries with `addSearch(field, op, value)` against a date or date/time field, pass the value as a string in `MM/DD/YYYY` format (US-order, slash-separated) — what BlueStep's `parseDate` documents as its default at `B.d.ts:2660`.
6
+
7
+ **Why:** Two BlueStep date formats coexist and they are NOT interchangeable for search input:
8
+
9
+ - `B.time.B6P_LOCAL_DATE = ofPattern("uuuu/MM/dd")` — the serialization/display format. `YYYY/MM/DD` strings ARE accepted by `addSearch` validation as "well-formatted" (no error thrown), but the comparison silently fails to apply — the predicate is dropped and all rows pass through unfiltered. Learned the hard way on the summitridge Report System (twice).
10
+ - `B.time.parseDate` default = `MM/dd/yyyy` — the actual parser BlueStep uses for date search values. This is the format `addSearch` wants.
11
+
12
+ ISO-8601 `YYYY-MM-DD` (with dashes, what HTML `<input type="date">` emits) throws the explicit error: *"Searching a date/time field requires a null, a date/time value or a String containing a well formatted date/time value."*
13
+
14
+ Passing a raw Java `LocalDate` object from `B.time.LocalDate` also fails — Graal.js's JS↔Java marshalling does not produce a usable string representation when the value flows into `addSearch`. (However, `B.time.ZonedDateTime` instances appear to work — see `gardenplazaofvalleyview\(DOR) Daily Occupancy Report` for a working example.)
15
+
16
+ **How to apply:**
17
+
18
+ - Format a JS `Date` to BlueStep search format as `MM/DD/YYYY` with slashes: `${m}/${day}/${y}`, zero-padded.
19
+ - Convert ISO `YYYY-MM-DD` from HTML date inputs to `MM/DD/YYYY` before calling `addSearch`.
20
+ - Working precedent: `rop\DPN Scoring\draft\scripts\app.ts:261` — `toMDY = (iso) => "${m}/${d}/${y}"`.
21
+ - Do NOT pass raw `LocalDate` / `LocalDateTime` Java objects to `addSearch` — always convert to BlueStep-format strings first.
22
+ - For datetime fields specifically, the format is unverified but likely `MM/DD/YYYY hh:mm:ss a` (the input-side analog of `B6P_LOCAL_DATETIME`'s output format). The summitridge Report System catalog has no datetime fields yet, so this is theoretical.
23
+
24
+ **The trap that bit twice on the summitridge Report System:**
25
+
26
+ 1. Initial bug 2026-04-24: passed ISO `YYYY-MM-DD` → got the well-formatted-date error → fixed by switching to `YYYY/MM/DD`.
27
+ 2. Regression 2026-04-26: `YYYY/MM/DD` made the error stop, but filters silently returned all rows. Real fix: switch to `MM/DD/YYYY`. Don't be fooled by the absence of an error — a date filter that's actually applied produces a *narrower* row count.
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: "Research-first approach for BlueStep endpoints — gather API docs, read type declarations, study existing examples before writing"
3
+ ---
4
+
5
+ For any new BlueStep endpoint or integration, front-load research before writing code: read the external API docs, study the `B.d.ts` declarations for HTTP/request/response patterns, and look at existing endpoint examples in the org. Write with full context.
6
+
7
+ **Why:** This avoids trial-and-error iteration on BlueStep's GraalJS environment, where testing requires pushing to the server. The research-first pass has produced correct code on the first try.
8
+
9
+ **How to apply:** Gather API docs + type declarations + existing examples up front. When delegating to a `bluestep-dev` agent, pass comprehensive context including exact method signatures and patterns.
@@ -0,0 +1,71 @@
1
+ ---
2
+ description: "Correct BSJS formula patterns — form access, field reads/writes, HTTP, error handling, B.* utilities, DocumentLinkField"
3
+ ---
4
+
5
+ Use these patterns as the baseline for every BlueStep post-save (or any) formula. Do not deviate without first checking a working formula — endpoints and merge reports behave differently, and formulas need special care.
6
+
7
+ **Why:** Hard-won from multiple failed iterations on a document-summarizer formula. The recurring failures were wrong form access and wrong HTTP response reading.
8
+
9
+ ## Structure
10
+
11
+ - Line 1 is ALWAYS: `/// <reference path='../../../scriptlibrary' />`
12
+ - Formulas run as bare top-level statements — NO IIFE, no `main()`, no `export default`.
13
+ - No bare `return` statements — use a guard `if` block, or `throw` inside try/catch, for early exits.
14
+
15
+ ## Form / field access
16
+
17
+ - A form/query configured in the component's platform form-import config (regenerated into `declarations/index.d.ts` on pull) is available as a **top-level variable directly in `app.ts`**, named after its FID — do NOT use `B.currentRecord()`. (Older modules registered this by hand in a now-legacy `objects/imports.ts`.)
18
+ - Fields: `myForm.fields.fieldName`.
19
+ - Field read: `.val()` (direct) or `.opt().orElse(default)` (safe).
20
+ - Field write: `.val(newValue)` for scalars (string, bool, date, number).
21
+ - Clear a field: `.clear()`.
22
+ - Single-select dropdown (200): `.val(exportValueString)` — `.options()` does NOT exist on these, it throws "Unknown identifier: options".
23
+ - Multi-select checkbox (201): `.options().filter(op => ...).forEach(op => op.selected(true))`.
24
+ - Set by ID: `.set(B.util.toId(idString))`.
25
+
26
+ ## HTTP + response reading
27
+
28
+ ```typescript
29
+ const stream = B.net.fetch(url, {
30
+ method: 'POST',
31
+ headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ...' },
32
+ body: JSON.stringify(payload),
33
+ connectionTimeout: 60_000,
34
+ readTimeout: 60_000
35
+ });
36
+ const responseText = B.io.fromInputStream(stream); // ← always use this, not BufferedReader
37
+ const data = JSON.parse(responseText);
38
+ ```
39
+
40
+ ## Error handling
41
+
42
+ ```typescript
43
+ try {
44
+ // logic — use throw "message" for controlled early exits
45
+ } catch (e) {
46
+ const sw = B.io.stringWriter();
47
+ if (e.printStackTrace) e.printStackTrace(B.io.toPrintWriter(sw));
48
+ else sw.write(e.toString());
49
+ myForm.fields.resultField.val('[Error: ' + sw.toString() + ']');
50
+ B.net.sendMessage('Formula failed: ' + sw.toString(), true); // modal alert to user
51
+ B.io.printStackTrace(e); // server log
52
+ }
53
+ ```
54
+
55
+ ## B.* utilities
56
+
57
+ - `B.io.fromInputStream(stream)` — read HTTP response to string.
58
+ - `B.io.stringWriter()` + `B.io.toPrintWriter(sw)` — capture Java stack traces.
59
+ - `B.io.printStackTrace(e)` — write exception to server log.
60
+ - `B.toBase64(javaByteArray)` — base64-encode a `Java.ByteArray`.
61
+ - `B.net.fetch(url, params)` — outbound HTTP.
62
+ - `B.net.sendMessage(html, true)` — modal alert to current user.
63
+ - `B.util.toId(string)` — convert a string to a `Bluestep.Id`.
64
+ - `B.time.ZonedDateTime.now()` — current timestamp.
65
+
66
+ ## DocumentLinkField
67
+
68
+ - Cast: `formEntry.fields.document as unknown as Bluestep.Relate.DocumentLinkField`.
69
+ - `docField.toBytes({})` — get the file as a `Java.ByteArray`.
70
+ - `docField.filename()` — get the filename string.
71
+ - `docField.contentSize()` — size in bytes (0 = no file attached).
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: "BlueStep pages use jQuery — never define a global $ function or variable in merge-report scripts or you silently break Save and all page interactivity"
3
+ ---
4
+
5
+ Never define a global function or variable named `$` in BlueStep merge-report scripts. BlueStep loads jQuery, and the entire page (form submission, change management, modals, navigation) depends on `$` being jQuery. Overwriting it with a `querySelector` wrapper silently breaks the Save button and all page interactivity.
6
+
7
+ **Why:** This caused a hard-to-diagnose bug where Save did nothing — jQuery's `$` was replaced by a plain `querySelector` helper, breaking `submitForm` and all BlueStep internals.
8
+
9
+ **How to apply:** Use `qs()` (or another non-colliding name) for `querySelector` helpers. Also avoid overwriting other common globals like `el` if they might collide with BlueStep or library internals.
@@ -0,0 +1,21 @@
1
+ ---
2
+ description: "Always pass the inner draft/ folder (not the component root) to push.js, otherwise files land in a draft/draft/* dead zone on BlueStep"
3
+ ---
4
+
5
+ When pushing a BlueStep component, always pass the **inner `draft/` folder** as the local-folder argument to `push.js`, never the component root.
6
+
7
+ ```bash
8
+ # CORRECT — relative paths come out as "scripts/app.ts", "static/script.ts"
9
+ node ~/.bluestep/push.js [--snapshot --comment "..."] \
10
+ "C:\...\Bluestep Pull Requests\<org>\<...path>\<Component Name>\draft" \
11
+ "https://<org>.bluestep.net/files/<id>/draft/"
12
+
13
+ # WRONG — relative paths come out as "draft/scripts/app.ts" and land in draft/draft/*
14
+ node ~/.bluestep/push.js "...\<Component Name>" "..."
15
+ ```
16
+
17
+ **Why:** `push.js` (`~/.bluestep/push.js`, lines 388–396) walks the supplied local folder and computes each file's path relative to it, then appends that relative path to the target URL. If the local folder is the component root (which contains `draft/` as a subfolder), every file's relative path starts with `draft/`, and concatenated with a target URL that already ends in `/draft/` you get `.../draft/draft/scripts/app.ts` — a parallel ghost tree BlueStep does not load from. The live files BlueStep serves live at the URL root (`.../draft/scripts/...`), not under a nested `draft/`.
18
+
19
+ **How to apply:** Every time you invoke `push.js` for a BlueStep component, the `localFolder` argument's last path segment must be `draft` (or `snapshot`). If you accidentally push at the wrong level, the symptom is silent staleness — pushes "succeed" but the live JS never updates, and browser caches and source maps mislead you into thinking the bug is in code that is actually fine. Diagnose by inspecting the BlueStep file tree for a duplicate `draft/` subfolder or unexpected files at the root.
20
+
21
+ **Related — the deriver fails when components are nested deeper than `<org>/<scriptName>`.** `push.js`'s URL deriver expects exactly three path segments before `draft/` (org / scriptName / type). The Report System uses `summitridge/Report System/<Component>/draft` — four segments — so auto-derive fails. Fix: read each component's `.b6p_metadata.json` and pass the `url` explicitly as the second argument. All three Report System components (Maestro, Viewer, Builder) need this on every push.
@@ -0,0 +1,17 @@
1
+ ---
2
+ description: "Don't pile CSS/HTML/JS into app.ts — use the dedicated files (styles.css, index.html, script.ts) that the pulled folder already has"
3
+ ---
4
+
5
+ When a BlueStep pull contains dedicated files (`styles.css`, `index.html`, `script.ts`, etc.) alongside `app.ts`, put each kind of content in the file that is named for it. Do not dump all CSS and HTML into a single giant `B.out = ` template literal in `app.ts`.
6
+
7
+ **Why:** The folder layout exists precisely so concerns are separated — CSS in `styles.css`, markup in `index.html`, client interactivity in `script.ts`. Stuffing everything into `app.ts` defeats the structure and makes the code unmaintainable. The user has called this out multiple times.
8
+
9
+ **How to apply:**
10
+
11
+ - Before writing or modifying a BlueStep file, list the folder contents and identify which dedicated files exist.
12
+ - Route content to its natural file: CSS → `styles.css`, markup template → `index.html` (or a separate template file), client-side JS → `script.ts`, server-side logic → `app.ts`.
13
+ - For merge reports specifically: the server-side TS can read sibling files via `B.io.fromInputStream(...)` (or the appropriate API) and inject them into `B.out` — investigate the existing API rather than inlining.
14
+ - For endpoints with `static/` folders: HTML/CSS/JS load from their own files; `app.ts` is the request handler only.
15
+ - If unsure how multiple files relate in a given BlueStep component type, check [api patterns](../reference/api-patterns.md) and look at how other working components are organized.
16
+
17
+ **Repeated offense — do not do this again.**
@@ -0,0 +1,28 @@
1
+ ---
2
+ description: "BlueStep's server-side build compiles only root static/script.ts to .build/script.js — subdirectory .ts files are NOT compiled"
3
+ ---
4
+
5
+ Keep all BlueStep merge-report client code in ONE file: `static/script.ts`. BlueStep compiles only the root `static/script.ts` → `.build/script.js`. It does NOT recursively compile `static/util/*.ts`, `static/pages/*.ts`, etc. — even though a `tsconfig.json` with `"include": ["**/*.ts"]` suggests it should.
6
+
7
+ Symptom when you get this wrong: silent 404s on every subdirectory `.build/*.js`, producing a completely blank page (no errors — the scripts just don't load).
8
+
9
+ **Why:** Discovered 2026-04-15 while building the CRM Intelligence Dashboard (beh/1433876). A modular architecture with `util/escape.ts`, `util/dates.ts`, `pages/overview.ts`, etc. produced an empty `.build/` and a blank page. Consolidating everything into a single `static/script.ts` (matching the Havenwood Census Dashboard pattern) fixed it immediately.
10
+
11
+ **How to apply:**
12
+
13
+ - Put all merge-report client code in one file: `static/script.ts`.
14
+ - Use banner comments to organize logical sections (TYPES, UTILITIES, COMPONENTS, PAGES, ENTRY POINT).
15
+ - `static/index.html` should load only `styles.css`, any CDN scripts (e.g. Chart.js), and `.build/script.js`.
16
+ - Server endpoint code follows the same rule: `scripts/app.ts` is the only runtime-loaded entry. Confirmed on server-side endpoints 2026-04-24 — a sibling `scripts/runAction.ts` containing runtime functions compiled to its own `.build/scripts/runAction.js`, but BlueStep never loaded it, so `handleRunAction is not defined` at runtime.
17
+
18
+ **Exception — pure type files are safe to keep separate:**
19
+
20
+ - A `scripts/types.ts` that contains ONLY `interface`/`type`/type-alias declarations (zero runtime emit) can live alongside `app.ts`. TypeScript sees it at compile time; no JS is emitted for it; BlueStep has nothing to load or miss. Verified on the summitridge Report System Maestro.
21
+ - The moment you add a runtime value (a `const`, `function`, or `class` that emits), it becomes invisible unless merged into `app.ts`.
22
+
23
+ **Gotcha — module-level `const` + top-level dispatcher = temporal dead zone (TDZ) errors:**
24
+
25
+ - If `scripts/app.ts` starts with a top-level `try { ... switch(action) { case "x": handleX(); } }` and `handleX` references a `const` declared *later* in the same file, Graal.js throws `ReferenceError: X is not defined` at runtime. Function declarations hoist; `const` bindings do NOT. See [top level const tdz](top-level-const-tdz.md).
26
+ - Fix: put all module-level constants ABOVE the top-level dispatcher block. Seen on the Report Data Maestro 2026-04-24 (`MAX_PAGE_SIZE`, `OPS_BY_TYPE`, etc.).
27
+
28
+ If the architecture is genuinely too big for one file, concatenate at author time (source section comments, clear banners) rather than relying on module resolution — BlueStep's compiler does not do it for you.