@eventmodelers/cli 1.0.13 → 1.0.17

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 CHANGED
@@ -105,6 +105,7 @@ npx @eventmodelers/cli re-init # refresh an already-install
105
105
  npx @eventmodelers/cli run # start the agent loop (ralph-claude.js) from the installed kit dir
106
106
  npx @eventmodelers/cli run --ollama # same, via local Ollama (ralph-ollama.js)
107
107
  npx @eventmodelers/cli run --bash # bash-only loop, no realtime (ralph.sh)
108
+ npx @eventmodelers/cli run --local # skip platform config/credential lookup entirely — local-only, no board sync
108
109
  npx @eventmodelers/cli fetch --context <name> # pull full slice detail for one context on the board into <kit-dir>/.slices/
109
110
  npx @eventmodelers/cli fetch --context <name> --slice-id <id> # same, then print just that slice
110
111
  npx @eventmodelers/cli fetch --context <name> --slice-title <title> # same, then print just the slice matching this title
package/cli.js CHANGED
@@ -1772,6 +1772,7 @@ program
1772
1772
  .option('--ollama', 'Use ralph-ollama.js instead of the default Claude runner (build-kit stacks only)')
1773
1773
  .option('--bash', 'Use the bash-only ralph.sh loop (build-kit stacks only, no realtime)')
1774
1774
  .option('--modeling', 'Keep one Claude process warm across prompts instead of spawning a fresh one per task, for low-latency voice/live use. Modeling-kit installs only — there is no cold-spawn/tasks.json loop for modeling-kit. Built into the CLI, not a per-project file.')
1775
+ .option('--local', 'Skip platform config/credential lookup entirely and run the local-only loop (no board sync, no realtime agent) — even if .eventmodelers/config.json has credentials (build-kit stacks only)')
1775
1776
  .option('--verbose', 'Log every tool call\'s full input (commands, skill args, file paths) and assistant reasoning text. Default is condensed, high-level per-step logging only.')
1776
1777
  .action(async (opts) => {
1777
1778
  const cwd = process.cwd();
@@ -1799,6 +1800,10 @@ program
1799
1800
  console.error('❌ --modeling is mutually exclusive with --bash/--ollama — those select a build-kit runner, which --modeling has no use for.');
1800
1801
  process.exit(1);
1801
1802
  }
1803
+ if (opts.local) {
1804
+ console.error('❌ --modeling has no local-only mode — it is always driven by the org-wide realtime prompt queue, so --local has no use for it.');
1805
+ process.exit(1);
1806
+ }
1802
1807
  if (!modelingKitDir) {
1803
1808
  console.error(`❌ --modeling only supports a modeling-kit install (${MODELING_KIT.kitDirName}/) — it subscribes to the org-wide prompt queue, which build-kit stacks don't have. Use \`eventmodelers run\` (optionally with --ollama/--bash) for build-kit's slice-status loop instead.`);
1804
1809
  process.exit(1);
@@ -1850,9 +1855,11 @@ program
1850
1855
  console.log(`▶ Starting ${relative(cwd, runnerPath)}...\n`);
1851
1856
  const cmd = runner.endsWith('.sh') ? `"${runnerPath}"` : `node "${runnerPath}"`;
1852
1857
  try {
1853
- // Only ralph-claude.js reads this — the bash loop and the ollama executor have
1854
- // their own separate output paths with no stream-json parsing to gate.
1855
- execSync(cmd, { cwd: kitDir, stdio: 'inherit', env: { ...process.env, RALPH_VERBOSE: opts.verbose ? '1' : '' } });
1858
+ // Only ralph-claude.js reads RALPH_VERBOSE — the bash loop and the ollama executor have
1859
+ // their own separate output paths with no stream-json parsing to gate. RALPH_LOCAL is
1860
+ // read by all three runners (ralph.js's startRalph, and ralph.sh directly) to force the
1861
+ // local-only branch even when .eventmodelers/config.json has valid credentials.
1862
+ execSync(cmd, { cwd: kitDir, stdio: 'inherit', env: { ...process.env, RALPH_VERBOSE: opts.verbose ? '1' : '', RALPH_LOCAL: opts.local ? '1' : '' } });
1856
1863
  } catch (err) {
1857
1864
  process.exit(err.status || 1);
1858
1865
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventmodelers/cli",
3
- "version": "1.0.13",
3
+ "version": "1.0.17",
4
4
  "description": "Eventmodelers CLI — real-time Claude agent + skills for Claude Code, for any stack (Node, Supabase, Axon, Cratis, or modeling-only)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,7 +10,7 @@ export async function createSupabaseRealtimeAdapter(cfg, initialToken) {
10
10
  await supabase.realtime.setAuth(initialToken);
11
11
 
12
12
  return {
13
- subscribe(topic, handlers, onStatus) {
13
+ async subscribe(topic, handlers, onStatus) {
14
14
  let channel = supabase.channel(topic, { config: { private: true } });
15
15
  for (const [event, handler] of Object.entries(handlers)) {
16
16
  channel = channel.on('broadcast', { event }, (msg) => handler(msg.payload));
@@ -397,15 +397,19 @@ async function ralphLoop(kitDir, cfg, onTask, onPlannedSlice) {
397
397
 
398
398
  export { loadLocalConfig, fetchPlatformConfig, retryOn401, startRealtimeAgent };
399
399
 
400
- export async function startRalph({ kitDir, projectDir, onTask, onPlannedSlice, agentType = 'BUILD', queueAllStatuses = false }) {
400
+ export async function startRalph({ kitDir, projectDir, onTask, onPlannedSlice, agentType = 'BUILD', queueAllStatuses = false, localOnly = false }) {
401
401
  const local = loadLocalConfig(kitDir);
402
402
  local.agentId = ensureAgentId(kitDir, agentType);
403
403
 
404
404
  console.log(`Ralph — kit: ${kitDir}`);
405
405
  console.log(` project: ${projectDir}`);
406
406
 
407
- if (!hasCredentials(local)) {
408
- console.log(` mode: local-only (no platform sync)\n`);
407
+ // localOnly (set via `eventmodelers run --local`) forces this branch even when
408
+ // credentials are present — it skips fetchPlatformConfig's network call to
409
+ // ${baseUrl}/api/config and startRealtimeAgent entirely, so the loop never
410
+ // reaches out to the platform at all.
411
+ if (localOnly || !hasCredentials(local)) {
412
+ console.log(` mode: local-only (no platform sync)${localOnly ? ' — forced by --local' : ''}\n`);
409
413
  await ralphLoop(kitDir, local, onTask, onPlannedSlice);
410
414
  return;
411
415
  }
@@ -96,6 +96,7 @@ startRalph({
96
96
  projectDir,
97
97
  onTask: runClaude,
98
98
  onPlannedSlice: runClaude,
99
+ localOnly: process.env.RALPH_LOCAL === '1',
99
100
  }).catch((err) => {
100
101
  console.error('[ralph] Fatal:', err);
101
102
  process.exit(1);
@@ -33,6 +33,7 @@ startRalph({
33
33
  projectDir,
34
34
  onTask: runOllama,
35
35
  // onPlannedSlice omitted — ollama-agent manages its own task queue
36
+ localOnly: process.env.RALPH_LOCAL === '1',
36
37
  }).catch((err) => {
37
38
  console.error('[ralph] Fatal:', err);
38
39
  process.exit(1);
@@ -21,7 +21,10 @@ BACKEND_PROMPT_FILE="$KIT_DIR/lib/backend-prompt.md"
21
21
  AGENT_SCRIPT="$KIT_DIR/lib/agent.sh"
22
22
 
23
23
  HAS_CREDENTIALS=true
24
- if [[ ! -f "$KIT_DIR/.eventmodelers/config.json" ]]; then
24
+ if [[ "${RALPH_LOCAL:-}" == "1" ]]; then
25
+ echo "[ralph] --local — platform sync disabled." >&2
26
+ HAS_CREDENTIALS=false
27
+ elif [[ ! -f "$KIT_DIR/.eventmodelers/config.json" ]]; then
25
28
  echo "[ralph] Note: no .eventmodelers/config.json found — platform sync disabled." >&2
26
29
  echo " To enable board sync, follow: https://app.eventmodelers.ai/documentation#build" >&2
27
30
  echo " Code generation from local slice definitions will still run." >&2
@@ -38,6 +38,10 @@ Read the target project's `.build-kit/CLAUDE.md` and explore existing slices. Lo
38
38
  - Feature flag patterns (Step 4 — optional)
39
39
  - Spring Boot test annotation: check if the project defines a meta-annotation over `@SpringBootTest`
40
40
 
41
+ **Determine `{basePackage}`** — every path below is rooted at
42
+ `{basePackage}.slices.{context}.automation.{slicename}`. Resolve `{basePackage}` as documented in
43
+ `.build-kit/CLAUDE.md`'s Structure section.
44
+
41
45
  ## Step 1: Understand the Input
42
46
 
43
47
  Extract these elements regardless of input format:
@@ -131,7 +135,7 @@ If a command fails, the event handler fails and the event processor retries.
131
135
  ### Stateless Automation
132
136
 
133
137
  New automation slices live under
134
- `src/main/java/de/eventmodelers/slices/{context}/automation/{slicename}/`.
138
+ `src/main/java/.../slices/{context}/automation/{slicename}/`.
135
139
 
136
140
  #### Strategy interface (if needed)
137
141
 
@@ -280,7 +284,7 @@ Add to ALL config files when using `@ConditionalOnProperty`:
280
284
  **For stateless automations** — pure unit tests with a mocked `CommandDispatcher`:
281
285
 
282
286
  ```java
283
- // File: src/test/java/de/eventmodelers/slices/{context}/automation/{slicename}/{AutomationName}Test.java
287
+ // File: src/test/java/.../slices/{context}/automation/{slicename}/{AutomationName}Test.java
284
288
  class {AutomationName}ProcessorTest {
285
289
 
286
290
  private {AutomationName}Processor processor;
@@ -19,7 +19,8 @@ description: >
19
19
  > **Preview feature** — AF5 Workflow APIs may change without notice and are not yet for production.
20
20
 
21
21
  New workflow slices live under
22
- `src/main/java/de/eventmodelers/slices/{context}/automation/{slicename}/`.
22
+ `src/main/java/.../slices/{context}/automation/{slicename}/` — see Step 0 below for how to resolve the
23
+ `{basePackage}` this path is rooted at.
23
24
 
24
25
  ---
25
26
 
@@ -32,6 +33,11 @@ Read `.build-kit/CLAUDE.md` and check whether the project already uses AF5 Workf
32
33
  - A `WorkflowModule` bean in any `@Configuration` class
33
34
  - Classes annotated with `@Workflow`
34
35
  - Existing `*Workflow.java` files under any slice package
36
+
37
+ **Determine `{basePackage}`** — every path in this skill is rooted at
38
+ `{basePackage}.slices.{context}.automation.{slicename}`. Resolve `{basePackage}` as documented in
39
+ `.build-kit/CLAUDE.md`'s Structure section.
40
+
35
41
  - The `WorkflowModule` dependency in `pom.xml`:
36
42
 
37
43
  ```xml
@@ -92,7 +98,7 @@ The simplest way to define a workflow using `@Workflow`:
92
98
 
93
99
  ```java
94
100
  // File: {slicename}/{SliceName}Workflow.java
95
- package de.eventmodelers.slices.{context}.automation.{slicename};
101
+ package {basePackage}.slices.{context}.automation.{slicename};
96
102
 
97
103
  import org.axonframework.workflow.annotation.Workflow;
98
104
  import org.axonframework.workflow.SimpleWorkflowContext;
@@ -100,7 +106,7 @@ import org.axonframework.workflow.SimpleWorkflowContext;
100
106
  @Workflow(
101
107
  idProperty = "{triggerEventIdField}", // field name on the trigger event that becomes the workflow ID
102
108
  startOnEventClass = {TriggerEvent}.class, // event that starts a new workflow instance
103
- workflowNamespace = "de.eventmodelers.slices.{context}"
109
+ workflowNamespace = "{basePackage}.slices.{context}"
104
110
  )
105
111
  public class {SliceName}Workflow {
106
112
 
@@ -333,7 +339,7 @@ public void execute(SimpleWorkflowContext ctx) {
333
339
 
334
340
  ```java
335
341
  @Workflow(idProperty = "id", startOnEventClass = OrderPlaced.class,
336
- workflowNamespace = "de.eventmodelers.slices.{context}")
342
+ workflowNamespace = "{basePackage}.slices.{context}")
337
343
  public class {SliceName}Workflow {
338
344
 
339
345
  public void execute(SimpleWorkflowContext ctx) { /* ... */ }
@@ -476,14 +482,14 @@ class {SliceName}WorkflowIntegrationTest extends AbstractDeclarativeTestBase {
476
482
  ## Files to Create / Modify
477
483
 
478
484
  ```
479
- src/main/java/de/eventmodelers/slices/{context}/automation/{slicename}/
485
+ src/main/java/.../slices/{context}/automation/{slicename}/
480
486
  ├── {SliceName}Workflow.java ← @Workflow class (execute method + lifecycle listeners)
481
487
  └── {SliceName}WorkflowServices.java ← @Component step handlers (Spring beans)
482
488
 
483
- src/main/java/de/eventmodelers/slices/{context}/
489
+ src/main/java/.../slices/{context}/
484
490
  └── WorkflowConfiguration.java ← WorkflowModule bean (one per context, create if missing)
485
491
 
486
- src/test/java/de/eventmodelers/slices/{context}/automation/{slicename}/
492
+ src/test/java/.../slices/{context}/automation/{slicename}/
487
493
  └── {SliceName}WorkflowTest.java ← unit tests with mocked context + services
488
494
  ```
489
495
 
@@ -35,12 +35,18 @@ Read `.build-kit/.slices/{context}/{slicename}/slice.json`. Extract, and use **o
35
35
 
36
36
  Never invent a field, business rule, or event that isn't in slice.json.
37
37
 
38
+ ## Step 0a: Determine `{basePackage}`
39
+
40
+ Every code example below is rooted at `{basePackage}.slices.{context}.{slicename}`. Resolve
41
+ `{basePackage}` as documented in `.build-kit/CLAUDE.md`'s Structure section — never hardcode
42
+ `io.axoniq.quickstart` (the shipped quickstart scaffold's package) or any other specific package.
43
+
38
44
  ## Step 1: Command
39
45
 
40
46
  **Exactly one field has `idAttribute: true` in slice.json** — annotate it directly:
41
47
 
42
48
  ```java
43
- package io.axoniq.quickstart.slices.{context}.{slicename};
49
+ package {basePackage}.slices.{context}.{slicename};
44
50
 
45
51
  import org.axonframework.messaging.commandhandling.annotation.Command;
46
52
  import org.axonframework.modelling.annotation.TargetEntityId;
@@ -61,13 +67,13 @@ one field directly.** Instead build a compound id record and put `@TargetEntityI
61
67
  verified against the `SubscribeToCourse` slice (`email` + `courseId` both `idAttribute: true`):
62
68
 
63
69
  ```java
64
- package io.axoniq.quickstart.slices.{context}.{slicename};
70
+ package {basePackage}.slices.{context}.{slicename};
65
71
 
66
72
  public record {SliceName}Id(String field1, String field2) {}
67
73
  ```
68
74
 
69
75
  ```java
70
- package io.axoniq.quickstart.slices.{context}.{slicename};
76
+ package {basePackage}.slices.{context}.{slicename};
71
77
 
72
78
  import org.axonframework.messaging.commandhandling.annotation.Command;
73
79
  import org.axonframework.modelling.annotation.TargetEntityId;
@@ -93,7 +99,7 @@ Check `src/main/java/.../{context}/events/` first; add to the existing sealed/ma
93
99
  than creating a duplicate.
94
100
 
95
101
  ```java
96
- package io.axoniq.quickstart.slices.{context}.events;
102
+ package {basePackage}.slices.{context}.events;
97
103
 
98
104
  import org.axonframework.eventsourcing.annotation.EventTag;
99
105
  import org.axonframework.messaging.eventhandling.annotation.Event;
@@ -119,10 +125,10 @@ Package-private, mutable field(s) — **not** an immutable `State` record with f
119
125
  check needs, nothing else.
120
126
 
121
127
  ```java
122
- package io.axoniq.quickstart.slices.{context}.{slicename};
128
+ package {basePackage}.slices.{context}.{slicename};
123
129
 
124
- import io.axoniq.quickstart.slices.{context}.events.{EventName};
125
- import io.axoniq.quickstart.slices.{context}.events.EventTags;
130
+ import {basePackage}.slices.{context}.events.{EventName};
131
+ import {basePackage}.slices.{context}.events.EventTags;
126
132
  import org.axonframework.eventsourcing.annotation.EventCriteriaBuilder;
127
133
  import org.axonframework.eventsourcing.annotation.EventSourcingHandler;
128
134
  import org.axonframework.eventsourcing.annotation.reflection.EntityCreator;
@@ -164,7 +170,7 @@ resolved automatically from `Configuration` — pass it plus the event class str
164
170
  tempting to think `andBeingOneOfTypes(new QualifiedName({EventName}.class))` should work without it,
165
171
  since `QualifiedName` has a `Class<?>` constructor. Verified experimentally that it does NOT:
166
172
  `QualifiedName(Class<?>)`'s only job is `clazz.getName()` — the raw Java class name
167
- (`io.axoniq.quickstart.slices.foo.events.CustomerRegistered`) — not the `@Event(namespace, name)` value
173
+ (`{basePackage}.slices.foo.events.CustomerRegistered`) — not the `@Event(namespace, name)` value
168
174
  the event was actually appended under (`Foo.CustomerRegistered`). Swapping to it in
169
175
  `SubscribeToCourseDecisionModel` as a test made 2 of 3 passing tests fail immediately, silently, with
170
176
  no exception pointing at the real cause — the criteria just stopped matching anything, identical in
@@ -220,9 +226,9 @@ itself.
220
226
  ## Step 4: Command handler
221
227
 
222
228
  ```java
223
- package io.axoniq.quickstart.slices.{context}.{slicename};
229
+ package {basePackage}.slices.{context}.{slicename};
224
230
 
225
- import io.axoniq.quickstart.slices.{context}.events.{EventName};
231
+ import {basePackage}.slices.{context}.events.{EventName};
226
232
  import org.axonframework.messaging.commandhandling.annotation.CommandHandler;
227
233
  import org.axonframework.messaging.core.Metadata;
228
234
  import org.axonframework.messaging.eventhandling.gateway.EventAppender;
@@ -255,7 +261,7 @@ exist as a type in this version.
255
261
  ## Step 5: REST endpoint — only if slice.json shows an inbound `SCREEN` dependency on the command
256
262
 
257
263
  ```java
258
- package io.axoniq.quickstart.slices.{context}.{slicename};
264
+ package {basePackage}.slices.{context}.{slicename};
259
265
 
260
266
  import org.axonframework.messaging.commandhandling.gateway.CommandGateway;
261
267
  import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -323,9 +329,9 @@ One-time `pom.xml` addition (no version needed — resolved via the project's ex
323
329
  ```
324
330
 
325
331
  ```java
326
- package io.axoniq.quickstart.slices.{context}.{slicename};
332
+ package {basePackage}.slices.{context}.{slicename};
327
333
 
328
- import io.axoniq.quickstart.slices.{context}.events.{EventName};
334
+ import {basePackage}.slices.{context}.events.{EventName};
329
335
  import org.axonframework.eventsourcing.configuration.EventSourcedEntityModule;
330
336
  import org.axonframework.eventsourcing.configuration.EventSourcingConfigurer;
331
337
  import org.axonframework.messaging.commandhandling.configuration.CommandHandlingModule;
@@ -9,7 +9,7 @@ directly, and tests use `WebTestClient`, not `MockMvc`.
9
9
  ## Controller
10
10
 
11
11
  ```java
12
- package io.axoniq.quickstart.slices.{context}.{slicename};
12
+ package {basePackage}.slices.{context}.{slicename};
13
13
 
14
14
  import org.axonframework.messaging.commandhandling.gateway.CommandGateway;
15
15
  import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -59,7 +59,7 @@ shape for the first one, matching the project's WebFlux stack and the `@Conditio
59
59
  feature-flag convention (tests disable all slices by default; opt in per test class):
60
60
 
61
61
  ```java
62
- package io.axoniq.quickstart.slices.{context}.{slicename};
62
+ package {basePackage}.slices.{context}.{slicename};
63
63
 
64
64
  import org.axonframework.messaging.commandhandling.gateway.CommandGateway;
65
65
  import org.junit.jupiter.api.Test;
@@ -21,6 +21,10 @@ description: >
21
21
 
22
22
  Before writing any code, read the target project's `.build-kit/CLAUDE.md`
23
23
 
24
+ **Determine `{basePackage}`** — every code example below is rooted at
25
+ `{basePackage}.slices.{context}.{slicename}`. Resolve `{basePackage}` as documented in
26
+ `.build-kit/CLAUDE.md`'s Structure section.
27
+
24
28
  ## Step 1: Ensure Events Exist
25
29
 
26
30
  Before implementing the read slice, verify that all events the projector handles exist in the
@@ -70,7 +74,7 @@ comments** — those are only for write slices.
70
74
  ### Slice package structure
71
75
 
72
76
  ```
73
- de/<package>/{context}/slices/{slicename}/
77
+ .../slices/{context}/{slicename}/ (i.e. {basePackage}.slices.{context}.{slicename} — see Step 0)
74
78
  ├── Get{SliceName}.java ← query record + nested Result
75
79
  ├── {SliceName}Summary.java ← read model (projection output shape)
76
80
  ├── {SliceName}Projector.java ← @Component with @EventHandler + @QueryHandler
@@ -252,7 +256,7 @@ Pure unit tests — instantiate the projector directly, no Spring context needed
252
256
  Fast, no container startup.
253
257
 
254
258
  ```java
255
- // File: src/test/java/de/<package>/{context}/slices/{slicename}/{SliceName}ProjectorTest.java
259
+ // File: src/test/java/.../slices/{context}/{slicename}/{SliceName}ProjectorTest.java
256
260
  class {SliceName}ProjectorTest {
257
261
 
258
262
  private {SliceName}Projector projector;
@@ -2,6 +2,13 @@
2
2
 
3
3
  Read Events in src/events to understand the global structure.
4
4
 
5
+ `<basePackage>` in this project's Java code (`src/main/java/<basePackage>/slices/...`) is this
6
+ project's own Java package prefix, not a fixed value — resolve it, in order: (1) the package of the
7
+ project's `@SpringBootApplication` class, (2) the package of any existing slice already under
8
+ `.../slices/{context}/{slicename}/`, (3) only if no code exists yet, Maven's `<groupId>` in `pom.xml`
9
+ or Gradle's `group` property in `build.gradle`/`build.gradle.kts`. Never hardcode
10
+ `io.axoniq.quickstart` (the shipped quickstart scaffold's package) or any other specific package.
11
+
5
12
  ## File Structure Constraints
6
13
 
7
14
  - **Strict Path Limitation**: if not instructed otherwise, only check `src/slices/{slicename}/*.ts`
@@ -68,12 +68,14 @@ Guidelines:
68
68
 
69
69
  ### Marks — only when the user explicitly asks for one
70
70
 
71
- The canvas has a native "Marks" feature (outline highlight, optional blur-outside spotlight) for calling out part of a screen. **Do not add marks by default.** Only apply one of the two effects below when the request explicitly asks to highlight/mark/call out/circle/spotlight or blur/obscure part of the screen (e.g. "highlight the submit button", "blur everything except the email field"). An ordinary "design a screen" request gets no marks.
71
+ The canvas has a native "Marks" feature (outline highlight, plus an optional "blur outside" or "white outside" spotlight) for calling out part of a screen — see `HtmlEditorModal.tsx`'s Highlight tool and `markBlur.ts`/`markStyles.ts` in the main app. **Do not add marks by default.** Only apply the effects below when the request explicitly asks to highlight/mark/call out/circle/spotlight, or blur/obscure/white-out part of the screen (e.g. "highlight the submit button", "blur everything except the email field", "white out everything but the header"). An ordinary "design a screen" request gets no marks.
72
72
 
73
- Since this skill only has a `pages`/`backgroundColor` field to send (no separate marks API), reproduce the same visual language directly as inline CSS on the target element(s) self-contained in the page HTML, same as any other styling in Step 3:
73
+ This skill only has a `pages`/`backgroundColor` field to send (no separate marks API — the native feature persists marks as board metadata, not through this render call), so reproduce the same visual language directly as inline CSS on the target element(s), self-contained in the page HTML same as any other styling in Step 3. No `<script>`/`<style>` tags are needed (and `<script>` is stripped anyway) — inline `style="..."` reproduces the same CSS the native feature injects:
74
74
 
75
- - **Mark / highlight an area** — add to the target element's `style`: `outline:4px solid <color> !important;outline-offset:1px;`. Default color `#e74c3c` (red) unless the user names one; other options mirror the app's mark picker: `#1e293b` (dark slate), `#2ecc71` (green), `#3b82f6` (blue), `#f1c40f` (yellow), `#ffffff` (white).
76
- - **Blur outside / spotlight an area** — add `style="filter:blur(6px) !important;"` to every other top-level sibling/section on the page so only the called-out element stays sharp. Combine with the outline above if the user asked to both mark and blur.
75
+ - **Mark / highlight an area** — add to the target element's `style`: `outline:4px solid <color> !important;outline-offset:1px;`. Default color `#e74c3c` (red) unless the user names one; other options mirror the app's mark picker (`ColorPicker.tsx`): `#1e293b` (dark slate), `#2ecc71` (green), `#3b82f6` (blue), `#f1c40f` (yellow), `#ffffff` (white).
76
+ - **Blur outside / spotlight an area** — add `style="filter:blur(6px) !important;"` to every other top-level sibling/section on the page so only the called-out element stays sharp.
77
+ - **White outside / spotlight an area** — same idea, but instead add `style="filter:brightness(0) invert(1) !important;"` to every other top-level sibling/section (collapses them to solid white — works uniformly across text, shapes, and images, unlike a plain background-color override). Use this only when the user says "white out" / "whiteout" rather than "blur" — the two are mutually exclusive per mark in the native tool, so never apply both blur and white filters to the same sibling.
78
+ - Combine the outline rule with either spotlight rule if the user asked to both mark *and* blur/white-out.
77
79
 
78
80
  Apply these only to the specific element(s) the request describes — don't guess at additional areas to call out.
79
81