Skip to content

Demo-readiness hardening + Flow Editor epic (round-trip, libdoc-per-env, panels) + Chinese locale#45

Merged
raffelino merged 49 commits into
mainfrom
chore/demo-readiness-bmad
Jun 15, 2026
Merged

Demo-readiness hardening + Flow Editor epic (round-trip, libdoc-per-env, panels) + Chinese locale#45
raffelino merged 49 commits into
mainfrom
chore/demo-readiness-bmad

Conversation

@raffelino

Copy link
Copy Markdown
Collaborator

Summary

This branch bundles three sequential BMAD epics. It is large (45 commits) but every change landed with tests and the touched suites are green locally; CI (build.yml test-unit + e2e.yml) is the authoritative gate.

Reviewers: the three sections below are independent and can be reviewed in order. The Flow Editor epic is the headline change.


1. Demo-Readiness epic (≈24 defects fixed at source, each with a regression test)

Adversarial QA audits across Execution, AI/Reports, Recorder, Heal, Debugger → root-cause fixes (no workarounds):

  • Execution: orphan-run reaper, cancel-during-prep, stderr-pipe deadlock, timeout misclassification, docker leak, commit-before-dispatch, WS count-prop locks.
  • AI/Reports: spec path-traversal, code-fence corruption, nested-suite flattening, keyword-tag pollution, LLM connect-timeout, rotated-key error.
  • Recorder/Heal/Debugger: confidence-scale gate + per-repo RBAC on heal, single SSE subscriber, atomic debug-start dedup, breakpoints in control structures, Go To wait_until.
  • Reproducible per-feature demo scenarios (areas A–K) + retrospective under _bmad-output/.

2. Demo video — real full-fledged execution

Rewrote the Playwright capture tour to perform genuine executions of every feature + edge cases (real runs, self-healing, debugger, SSO/IdP, etc.) instead of caption teasers. Root cause of the prior broken render: login() never cleared first_login_complete, so every route bounced to /welcome; also switched networkidledomcontentloaded (live pages hold WebSockets).

3. Flow Editor epic — Verification & Hardening + 4 follow-ups + Chinese locale

The headline work. Story docs under _bmad-output/planning-artifacts/flow-editor-*.

  • Round-trip fidelity: extracted the .robot parse/serialize into a pure, unit-tested robotTextIO.ts; fixed silent-corruption bugs (inline comments → escaped args; column-0 comments → test-case names; control-line comments dropped; [Template] cell #/double-space/after-rows). Golden-corpus identity tests.
  • libdoc-per-environment keyword discovery: new GET /environments/{id}/keywords runs robot.libdoc in the env venv (offline-first, no rf-mcp), DB-cached with packages_changed_at invalidation + background task. Palette sources from it; shadowing fixed (project > library > BuiltIn).
  • Inline *** Variables *** + suite-settings panels in the Flow tab.
  • FE-BDD (Given/When/Then awareness), FE-ENV (%{} awareness), FE-TPL ([Template] data-row table), FE-KWSRC (libdoc universal, static map = bootstrap).
  • Chinese (zh / 中文) locale + header switcher.
  • Code-review pass (3 adversarial layers): fixed HIGH AC-B3 regression (inline edits reset the active test case), libdoc read-path subprocess, in-flight dispatch guard, + 4 more. Deferred items in _bmad-output/implementation-artifacts/deferred-work.md.

Testing

  • Frontend: 799 vitest green · vue-tsc --noEmit clean.
  • Backend: new libdoc 16 pytest + 198 environments-dir green; ruff/mypy clean on new code (lint below baseline on touched files).
  • E2E: flow-editor specs green (round-trip, file-settings incl. AC-B3 regression, control-structures, template, BDD, env-vars, libdoc palette, Chinese).
  • Full backend pytest (~1980) + full e2e deferred to CI.

Deferred (logged, non-blocking)

FQN Library.Keyword disambiguation · multi-line [Tags]/[Setup] ... continuation (pre-existing) · template-cell-equals-control-marker (RF-ambiguous).

🤖 Generated with Claude Code

raffelino and others added 30 commits June 13, 2026 23:22
… 1, Analyst)

Complete function inventory of RoboScope across backend, frontend,
recorder/heal/debugger, and ops/integrations/E2E (4 parallel BMAD Analyst
agents). Persisted backend detail + a demo-readiness matrix (areas A-K,
every feature with demo entry point + edge cases + status) that drives the
iterative QA/fix/demo/regression loop toward the demo-video goal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
BMAD QA edge-case audit of the highest-risk untested paths surfaced real
demo-breaking defects in run execution; fixed at the source:

- C1 (CRITICAL): a cancel during prepare()/sync was erased — execute()
  reset `_cancelled=False` and ran the whole suite despite CANCELLED. Both
  runners now short-circuit on a pre-set cancel and never reset it; tasks
  honors result.cancelled even if the cancelling commit hasn't propagated.
- C2 (CRITICAL): SubprocessRunner read stderr only after wait() → full-pipe
  deadlock on stderr-heavy runs, mis-reported as a hang. stderr is now
  drained concurrently in its own thread.
- H1 (HIGH): inactivity timeout mis-filed as FAILED (status sniffed from a
  "timeout" substring; the message says "hung"). New RunResult.timed_out
  flag; tasks classifies TIMEOUT from the flag.
- H2 (HIGH): docker wait(timeout=) is an HTTP read timeout that doesn't stop
  the container (leak + misclassification). Detect timeout by exception type
  → stop the container + flag timed_out.
- H3 (HIGH): commit-before-dispatch violated on TaskDispatchError (flush
  only) → run could strand in PENDING. Explicit db.commit() on both paths.

Tests: new tests/execution/test_execution_robustness.py (6, all fail
pre-fix incl. a real-subprocess stderr-deadlock test); updated the mocked
subprocess tests to the concurrent stderr-drain API (stderr.readline, not
the old post-wait readlines). Affected execution files: 55 passed.

Baseline (pre-fix, main): backend 2054 passed / frontend vitest green.
Artifacts: demo-readiness inventory + matrix + QA findings + iteration log.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
reconcile_interrupted_runs() marks PENDING/RUNNING runs as ERROR at
startup. The _active_runners registry is in-memory, so after a backend
restart any such row is an orphan whose worker died — left alone the UI
shows a permanently-spinning run. Wired into the lifespan after
create_tables(). Tested: orphans -> ERROR, terminal rows untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- M1: lock the websocket count properties (connection_count /
  run_connection_count) — unlocked reads could raise 'changed size during
  iteration' under concurrent connect/disconnect.
- M3: decode docker logs with an incremental UTF-8 decoder + newline buffer
  — per-chunk decode corrupted multibyte UTF-8 (accented DE/FR/ES test
  names) and emitted partial lines to the live output stream.
- L1: cap RLIMIT_DATA (4 GB) instead of RLIMIT_AS (2 GB) — RLIMIT_AS counts
  mmap'd virtual memory, which Chromium/Node reserve heavily, so the 2 GB
  AS cap could stop Browser-library tests from launching.

Tests: M3 incremental-decode regression test; 61 passed across
docker+websocket+robustness suites.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per-area reproducible demo-walkthrough format keyed to the committed
example fixtures (backend/examples) and the take-demo-video/screenshots
harness. Area files authored as each area's QA pass confirms behavior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
E2E triage (296 passed / 2 failed / 6 skipped) — both failures fixed at
source, not via test workarounds:

- stats (consistent fail): the toolbar refresh button and the stale-banner
  refresh CTA both rendered the accessible name 'Aktualisieren' (stats.refresh),
  an a11y defect (strict-mode ambiguity, confusing in a demo). The banner CTA
  now uses a distinct stats.refreshNow label in EN/DE/FR/ES.
- execution-run (flaky): the assertion waited on the transient 'Testlauf
  gestartet' toast (auto-dismisses); switched to the persistent
  .run-overlay-success overlay + cold-run timeout.

Verified: frontend vitest 734 passed, prod build clean (i18n escape gate);
targeted E2E rerun 18 passed. Both failures confirmed NOT regressions from
the execution fixes (POST /runs 201 x8, no 500s).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reproducible per-feature walkthroughs incl. edge cases, keyed to seeded
Examples fixtures and tied to the regression tests / e2e specs. Area D
documents the C1/C2/H1/H2/H4/M3 edge cases as concrete demo steps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reproducible per-feature demo walkthroughs + edge cases for every matrix
area, keyed to seeded fixtures and the e2e specs. All 11 areas (A-K) now
have demo scenarios on disk (DoD: each feature individually demonstrable).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The only specs failing across repeated local full runs are the real-run
execution tests (execution-run POST/overlay) — timing/load-sensitive, pass
in isolation, fail under a contended local machine + the documented
single-worker task queue (not app regressions: POST /runs 201, runs
complete, no 500s). Hardened pollRunToCompletion to 220s + 260s test budget
(execution-run + heal-toggle) and the run-overlay wait to 30s. The
authoritative E2E gate is CI e2e.yml on a clean runner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On clean CI runners: e2e.yml (full Playwright) and build.yml (backend
pytest 3.12+3.13 + frontend vitest + all dist builds) both succeed with all
Passes 1-7 changes. Confirms the local full-run flakes were environmental.
DoD 'existing E2E pipeline runs fully green' is met on this branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…er, LLM errors (Pass 9-10)

AI (Pass 9):
- C1 CRITICAL: write_generated_file/update_spec_hash now contain writes to
  the repo (_contained_target) — a user-authored spec target_file like
  ../../../x could otherwise write arbitrary files.
- C2 CRITICAL: _strip_code_fences extracts the balanced fenced block and
  tolerates a prose preamble/epilogue — the old line-0/last-line stripping
  left a dangling fence and produced an unparsable .robot.
- H4: malformed/empty LLM responses (empty choices / missing content) raise a
  clear error instead of an opaque IndexError/KeyError.
- M1: AI dispatch-failure path commits the failed job (was flush-only).

Reports (Pass 10):
- H1: nested suite names are hierarchical again (recurse direct child suites;
  the descendant iterator had flattened them, colliding same-named tests).
- H2: test tags exclude keyword-level tags (read only the test's own tags).

Tests: test_ai_robustness.py, TestEmptyContentHandling, test_parser_hierarchy.py.
Regression: AI affected files 76 passed + 49 robustness/llm; reports 26 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
build.yml on a clean runner passes backend pytest 3.12+3.13 + vitest + all
5 dist builds with the accumulated Execution+AI+Reports fixes. No regression.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- M3: LLM calls use httpx.Timeout(connect=5s, read=120s) and translate
  ConnectError/TimeoutException into a clear "provider unreachable / timed
  out" message. The old flat timeout=300 let a misconfigured/unreachable
  provider (e.g. Ollama not running) block the single max_workers=1 executor
  for 5 minutes, starving all other background tasks.
- H3: decrypt_api_key raises a typed ApiKeyDecryptError with a "re-enter the
  provider key" message instead of an opaque cryptography InvalidToken when
  SECRET_KEY changed since the key was saved.

Tests: connect/timeout + rotated-key tests added; existing wrong-key test
updated to the new typed error. ai encryption+robustness+llm: 67 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The test flipped the LAST base64url char of the token; that char carries
'don't care' low bits (3-byte to 4-char alignment), so a different char
sometimes decodes to the SAME signature bytes and the HMAC still verifies —
making the test flaky (it failed on the Python 3.12 CI leg while passing on
3.13). Perturb the FIRST char instead (fully significant bits), which always
changes the decoded payload/signature. 30/30 runs green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…fixes (Pass 13/14)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ass 15, C1+H1)

Flagship QA audit of Recorder/Heal/Debugger. Two heal defects fixed at source:

- C1 (CRITICAL): recorder sidecars store quality_score on a 0-100 scale, but
  the heal confidence gate (pick_best_candidate threshold 0.7/0.5) works in
  0-1. candidate_finder used the raw 0-100 number as confidence, so every
  sidecar candidate (e.g. 60.0) passed the 0.7 threshold — the "confidence
  thresholds gate every swap" safety invariant was fully bypassed on recorded
  tests. Normalize /100 (tolerating an already-0-1 value).
- H1 (HIGH): get_run_heal_report (get_current_user) and apply_heal_patch
  (global require_role(EDITOR)) bypassed per-repo effective-role RBAC — a
  global EDITOR without a grant on the run's repo could read heal data and
  write .robot files cross-repo. Switch both to require_effective_role_for_run
  (VIEWER / EDITOR), mirroring cancel/retry.

Tests: tests/execution/test_heal_confidence_scale.py — the FIRST unit tests
for the vendored RoboScopeHeal candidate_finder (previously zero coverage);
proves normalization + that the threshold now gates a low-quality sidecar.
Existing heal apply/report regression: 20 passed (RBAC swap keeps legit access).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ath guard (Pass 16, H3+M1)

- H3 (HIGH): output_xml_walker only descended <kw> elements, so a failure
  nested in an RF 7.x FOR/IF/TRY/WHILE block was invisible and DEBUG-2 set
  the breakpoint at the test header instead of the failing line. Recurse
  through control-structure containers; only <kw> leaves carry source/line.
- M1 (MED): the debug file path-traversal guard used str.startswith, which a
  sibling dir (/data/repo-secrets vs /data/repo) passes. Use relative_to.

Tests: tests/debug/test_output_xml_walker.py (plain kw / FOR / IF / none).
Debug suite regression: 68 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…etry (Pass 17, M2+M3)

- M2: the recorder emitted wait_until=domcontentloaded only on the synthesised
  first New Page; later Go To navigations inherited RF Browser's default
  wait_until=load, which hangs past the 10s timeout on ad-heavy pages. Every
  Go To now gets wait_until=domcontentloaded (honouring an explicit value).
- M3: heal _should_retry matched bare "timeout"/"locator(", firing on
  navigation/assertion timeouts and generic Playwright reprs — non-selector
  failures where a heal swap risks clicking the wrong element. Narrowed to
  selector-resolution signatures (resolved to 0 elements, waiting for
  selector, strict mode violation, ...).

Tests: test_robot_emit::TestGoToWaitUntil, test_heal_should_retry. 44 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…status)

Consolidates the 17-pass demo-readiness epic: inventory, 4 QA audits, 24
root-cause fixes with regression tests, demo scenarios for all 11 areas, and
the green CI pipeline. Records DoD status + documented niche follow-ups.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…8, H5)

_is_already_disambiguated returned True for any `>>`/`>>>`, so an unverified
multi-match chained selector (e.g. a shadow chain `.host >> .inner`) skipped
the defensive `>> nth=0` wrap and crashed Browser-library strict mode at
replay. Only a real nth marker (>> nth=, :nth-match(, :nth-of-type() now
counts as disambiguated; a bare pierce does not.

Tests: test_robot_emit::TestChainedSelectorDisambiguation (3).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ses (Pass 19, H4)

DebugSessionManager.start did the dedup at the router (find-then-start), which
is racy: two near-simultaneous starts (double-click / retry) both passed the
pre-check and both spawned a robotcode subprocess, leaking the first. The
dedup now runs inside the manager lock and raises DuplicateDebugSessionError
before any spawn; both run-based router start paths catch it and return 409.

Tests: tests/debug/test_session_manager.py (first tests for the manager) —
the dedup raises before the factory runs (no second subprocess). Debug suite:
70 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The recording command-stream documented a single-subscriber rule but never
enforced it: two EventSources (duplicate tab / reconnect race) both pulled
from the one SimpleQueue, so each saw only part of the recording. Add a
per-session active-subscriber flag (try_acquire_subscriber /
release_subscriber under the registry lock); the SSE endpoint returns 409
for a second concurrent subscriber and releases the slot in a finally so a
legitimate reconnect after the stream ends still attaches.

Tests: tests/recording/test_v2_command_queue_subscriber.py. Recording suite:
410 passed.

This closes the last HIGH-severity finding — all CRITICAL + HIGH across the
four QA audits are now fixed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s 21, M5+L1)

- M5: _split_iframe_wrap matched only " >>> " (spaced); an iframe selector
  with any other spacing around >>> was left unwrapped, so iframe-recorded
  heals (consent banners) silently never fired. Spacing-tolerant regex now.
- L1: the legacy recorder generator emitted a literal "***" for password
  fields — the generated test typed three asterisks and the login failed.
  Emit a ${PASSWORD} variable + a CHANGE_ME placeholder definition; the real
  captured secret is still never written.

Tests: test_heal_iframe_split.py (4), test_generator TestPasswordVariable (2),
updated test_password_masked. 20 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…, CI green

~30 root-cause defects fixed across 21 passes (4 QA audits); flagship audit
cleared (heal/debugger/recorder). C2/M4/L2 accepted/deferred with rationale.
Full CI pipeline green on a clean runner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lete

The March-era demo-video tour aborted at the run-creation modal (UI drift):
one unguarded click hit the 8s actionTimeout and failed the whole recording.
Guard + swallow every run-start interaction (the flow itself is covered by
execution-run.spec.ts) and Escape any leftover modal, so the Full Feature
Tour records end-to-end. Re-rendered a current EN demo video (3:20).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend the demo-video tour with a 'Deep Dive — every feature & edge case'
section driven by the demo-readiness matrix: Auth/RBAC (wrong-creds error,
rate-limit, real VIEWER read-only login, i18n), Explorer/Editors edge cases,
Heal (opt-in, confidence/budget gating, suspect-heal, no-heal tag), Debugger
(re-run-to-error, run-up-to-here, prereq install), Recorder (transport
gating, shadow/cross-frame, single-subscriber), Execution edge cases (cancel,
schedules + invalid cron), Environments (validation, browser/heal/offline
pack), Reports/Stats/History (upload guards, flakiness/quarantine),
Enterprise (SSO dry-run, teams, emergency bypass). Captions are now blocking
(awaited) so each feature/edge-case overlay is readable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The demo tour rendered the /welcome onboarding screen on every page because
login() never cleared `first_login_complete` for the seeded admin — the router
guard bounced every route to /welcome. This nullified both the overview tour
and the deep-dive (frames showed only the dashboard with captions).

- login(): PATCH /auth/me/first-login-complete=true so navigation lands on the
  real feature pages.
- Execution scene: dispatch the two demo runs through the proven POST /runs API
  path (guaranteed real Robot Framework execution + output + reports), show the
  run-creation modal as the visual, poll real status to terminal, then open the
  failed run's detail + real Output modal.
- repoId resolver prefers the local "Examples" repo (has the .robot files on
  disk) over the possibly-empty git-clone variant.
- Users scene: click the Settings "Benutzer" sub-tab directly (no nav-more-toggle).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The first-login fix exposed two follow-ups when real pages finally rendered:

- Real feature pages hold live WebSocket/SSE connections, so `networkidle`
  never settled and every nav()/reload()/goto burned its full 30s timeout —
  the DE render ballooned to ~32 min (incl. ~8 min stuck on /login). Switch all
  13 waitUntil/waitForLoadState calls to `domcontentloaded`.
- Debugger scene re-declared `failedId` (already declared in the execution
  scene) → TS duplicate-declaration → no tests collected. Reuse the variable.
- Users scene: drop the spurious `.nav-more-toggle` click.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ix comment corruption

Story: Flow Editor — Verification & Hardening (P0 round-trip).

Extract the RF text parse/serialize path out of RobotEditor.vue (3486 LOC) into
a pure, unit-tested module `robotTextIO.ts` — single source of truth, no more
duplicate logic that drifts (the failure mode the RobotEditorEscapeRoundTrip
test had to guard against by re-implementing helpers verbatim).

Fixes two silent-corruption bugs surfaced by the new golden-corpus test:
- Inline trailing comments (`Click  ${sel}  # note`) were swallowed as an arg
  and escaped to `\# note`. Now preserved on `RobotStep.trailingComment` and
  re-emitted verbatim.
- A column-0 comment inside `*** Test Cases ***` / `*** Keywords ***` became an
  item literally NAMED `# comment`. Now buffered as `leadingComments` on the
  next item (or `form.trailingComments` if dangling) and re-emitted.

Round-trip contract documented + pinned: idempotency over a real golden corpus
(byte-stable second pass), structural-equality re-parse, and regression pins for
escapes, `${}`/`@{}`/`&{}`/`%{}` variables, `...` continuation folding, and
nested FOR/IF/ELSE/END + RETURN.

Adds the BMAD story doc capturing the party-mode decisions and the full AC/test
plan for the remaining increments (file-settings+Variables inline, libdoc-per-
environment keyword discovery, control-structure e2e).

Tests: 754 frontend vitest green (incl. 20 new FlowEditorRoundTrip), vue-tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Story: Flow Editor — Verification & Hardening (P0, Winston's offline-first path).

Makes the Flow Editor's keyword palette able to show keywords from BOTH standard
and third-party libraries (Browser, SeleniumLibrary, …) WITHOUT depending on the
optional rf-mcp live server — RoboScope is offline-first.

- `keyword_introspection.py`: runs `robot.libdoc` (one subprocess) inside the
  environment's own venv over BUILTIN_LIBRARIES + every identified installed RF
  library, returning {name, library, args, shortdoc}. Libraries that can't be
  imported are skipped, never fatal.
- `EnvironmentKeywordCache` model + alembic migration: one row/env, cached
  keywords + a `source_hash` (digest of the venv's installed name==version set);
  drift → stale → re-introspect. uv-lockfile-equivalent invalidation.
- `GET /environments/{id}/keywords`: returns the fresh cache immediately; on
  miss/stale/refresh dispatches a background rebuild (`introspect_keywords_task`)
  and returns the current cache with status "building" so the UI renders what's
  there and polls for the rest. commit-before-dispatch honoured.

Tests: 15 pytest (hash determinism + drift, target-library selection, subprocess
parse/bad-json, cache rebuild persist/error, endpoint ready/building/404/auth).
ruff + mypy clean on all new code; zero new lint errors in touched files.

Frontend palette wiring + project>library>BuiltIn shadowing fix follow next.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
raffelino and others added 19 commits June 14, 2026 16:46
…n + env-keywords client

Story: Flow Editor — Verification & Hardening (P0 shadowing fix + libdoc client).

Fixes the long-standing shadowing bug (the EDITOR-2 TODO): a project/resource
keyword sharing a BuiltIn name showed the BuiltIn's signature, so the editor
offered the wrong args → broken tests. useKeywordSignatures now mirrors Robot
Framework's own search order — project/resource > library > BuiltIn — building
the map lowest-precedence first so higher tiers overwrite on a name clash.

- Promote project keywords into the explorer store (`projectKeywords` +
  `setProjectKeywords`) so the signature composable can see them; KeywordPalette
  pushes its loaded project keywords there.
- Add `getEnvironmentKeywords()` API client for the new libdoc-per-environment
  endpoint (offline-first palette source; consumption wiring follows).

Tests: 758 frontend vitest green (+4 shadowing-precedence cases), vue-tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-per-env

Story: Flow Editor — Verification & Hardening (completes #4 end-to-end).

preloadKeywords now resolves the repo's environment (own environment_id, else
the default env) and prefers GET /environments/{id}/keywords (libdoc cache —
works without rf-mcp). Falls back to the rf-knowledge search when the env cache
is still building/empty, no env resolves, or the endpoint errors — so the
palette is never worse than before and now shows foreign + standard library
keywords offline.

Tests: +4 explorer.store specs (ready→use libdoc, building→fallback,
no-env→fallback, error→fallback); 762 frontend vitest green, vue-tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Story: Flow Editor — Verification & Hardening (P0 AC-B).

Closes the two-editors gap: suite variables and suite-level settings are now
editable in the Flow tab, mirroring the existing Libraries panel (the
consistent "settings = toolbar panels" model).

- Variables panel (📦): list / add / edit / remove *** Variables *** entries;
  bare names auto-wrap to ${NAME}; edits commit on blur/Enter (never a
  continuous v-model into the form, matching the libraries-panel discipline).
- Suite-settings panel (⚙️): Suite Setup/Teardown, Test Setup/Teardown, Force/
  Default Tags, Documentation, Metadata, Test Timeout — quick-add chips for
  absent keys, inline value edit, remove.
- i18n keys in EN/DE/FR/ES (i18n-completeness test green).
- e2e flow-editor-file-settings.spec.ts (lists/adds/removes); serialization
  round-trip already pinned by the robotTextIO unit tests.

Tests: 762 frontend vitest green, vue-tsc clean. e2e runs in the green gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Story: Flow Editor — Verification & Hardening (P1 AC-D).

Closes the control-structure coverage gap (previously the nesting frames had
no round-trip test). Confirms the parser/serializer conserve nested blocks +
matching ENDs — no fix needed, the logic was correct, it was just unpinned.

- Unit FlowEditorControlStructures.spec.ts (5): FOR-in-IF + 2 ENDs,
  TRY/EXCEPT AS/FINALLY with exc var, WHILE limit=, END-matching at depth 3
  (FOR>IF>FOR, byte-stable double round-trip), BREAK/CONTINUE in a loop.
- e2e flow-editor-control-structures.spec.ts: live flow→code round-trip keeps
  IF/FOR/ELSE/TRY/EXCEPT/FINALLY + exactly 3 ENDs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…table, kw-source)

Splits the deferred items from the hardening story into 4 sprint-ready stories
with ACs + task + test plans: FE-BDD (Given/When/Then awareness), FE-ENV (%{}
env-var awareness), FE-TPL ([Template] data-row table), FE-KWSRC (libdoc as the
universal keyword source, shrink the static fallback).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Story FE-BDD. Recognises BDD prefixes (Given/When/Then/And/But) on keyword
steps: the node renders a prefix badge with the keyword shown sans prefix, and
signature lookup falls back to the prefix-stripped name (so `When Login` shows
`Login`'s args) while a keyword literally named with the prefix still wins.
Round-trip keeps the prefix verbatim — purely visual + resolution sugar, no
dedicated BDD authoring mode (per the story's scope).

Tests: +5 unit (splitBddPrefix matrix, converter flag, signature fallback +
verbatim preference); e2e flow-editor-bdd (3 badges + round-trip). 772 vitest
green, vue-tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Story FE-ENV. Recognises RF environment-variable references %{NAME} /
%{NAME=default}: a node shows a %{} indicator (tooltip lists the names +
defaults), and the Variables panel gains a read-only "Environment variables
used" summary for the active item. Round-trip keeps %{} verbatim. Read-only
awareness only — OS env values aren't edited here.

Tests: +7 unit (extract/collect matrix, converter flag, round-trip); e2e
flow-editor-env-vars (badge + panel summary). 779 vitest green, vue-tsc clean,
i18n EN/DE/FR/ES.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e table

Story FE-TPL. Fixes the one deferred item that was mis-MODELLED: data rows of a
[Template] test were parsed as bogus keyword steps. Now:

- robotTextIO: RobotTestCase.templateRows; parser routes a templated test's
  body rows into templateRows (control structures inside stay real steps),
  serializer re-emits them; `...` continuation extends a row. Round-trip pinned.
- flowConverter: emits a `template-table` node (Start → table → End) carrying
  the keyword + rows.
- FlowEditor: editable table node — edit cell on blur, add/remove row, add
  column; structural edits rebuild the graph.
- Fix: selectedNodeData returns null for `template-table` (selecting it had
  crashed the step detail panel with a toUpperCase-on-undefined render error).
- i18n EN/DE/FR/ES.

Tests: +6 unit (parse/round-trip/continuation/control-stays-step/converter
node); e2e flow-editor-template (table renders, add row round-trips to code).
790+ vitest green, vue-tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… static map is bootstrap-only

Story FE-KWSRC. Documents + pins that RF_KEYWORD_SIGNATURES is a STANDARD-
LIBRARY-ONLY bootstrap used before an environment is introspected; the
libdoc-per-environment endpoint is the universal source (resolution order:
project > libdoc(env) > bootstrap). Adds RF_BOOTSTRAP_SIGNATURES alias to make
the role explicit without import churn. "Shrink, don't grow" — a test pins that
no third-party keywords (Browser/Selenium) leak into the static map.

Tests: +5 unit (bootstrap invariants, no-third-party, libdoc-overrides-bootstrap);
e2e flow-editor-libdoc-keywords (env endpoint → palette shows libdoc keyword,
no rf-mcp). vitest green, vue-tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a Simplified-Chinese locale, registers it in the i18n config (with an
English-then-German fallback chain for zh), and offers ZH in the header
language switcher. The locale is deep-merged over the English base so EVERY
key resolves — high-traffic UI (common, nav, auth, dashboard, notifications,
flow-editor panels, welcome) is translated; the long tail falls back to English
under zh and can be extended over time. vue-i18n placeholders ({name}, {email})
and the escaped `@` are preserved.

Tests: +3 unit (ZhLocaleParity — every en key resolves under zh, spot
translations, placeholders intact); e2e i18n-chinese (ZH switch renders 中文 +
persists across reload). 793 vitest green, vue-tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ing)

Edge-case-hunter follow-up on the flow-editor work. Each fixed at source with a
fail-before/pass-after regression test (FlowEditorRoundTripEdgeCases.spec.ts):

1. Inline comment on a CONTROL line (`IF ${x}    # note`, FOR/WHILE/…) was
   parsed onto step.trailingComment but the serializer only re-emitted it for
   keyword/assignment → dropped. serializeStep now applies trailers uniformly.
2. A [Template] data cell starting with '#' serialized unescaped → RF read it
   as a comment at runtime. New escapeRfCell/unescapeRfCell escape leading '#'.
3. A [Template] data cell with an internal 2+-space run split into extra
   columns on round-trip. escapeRfCell now escapes those spaces as `\ `.
4. [Template] declared AFTER its data rows mis-parsed the earlier rows as
   keyword steps. Parser now looks ahead within the test body for [Template]
   and classifies all rows as data.

Tests: +5 edge-case unit; 798 vitest green; template + control e2e green;
vue-tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ead-path, round-trip)

3-layer pre-merge review (Blind Hunter + Edge Case Hunter + Acceptance Auditor)
of the flow-editor epic. 7 patch findings fixed at source, 3 deferred, 4 dismissed.

HIGH (AC-B3 / FE-TPL AC5): inline Variables/suite-settings/template-cell edits
mutated props.form directly, firing the deep watcher that resets activeItemIndex
to 0 and clears the selection — so editing a variable while on test case #2
yanked the canvas back to #1. (The earlier e2e missed it: fixtures had a single
test case, so reset-to-0 was invisible.) Added keepActiveItemOnRebuild() to all
direct mutations; new e2e uses TWO test cases to pin it.

MED: keyword_cache_is_fresh no longer shells out to `uv pip list` on every
GET /keywords — compares the env's packages_changed_at against the cache
updated_at; redundant introspection dispatch suppressed while a build is
in-flight (120s); empty [Template] no longer pulls body rows into data mode.

LOW: EXCEPT catch-all (`AS` at idx 1) keeps its var; task count = real keyword
count; tab round-trip test pinning documented normalization.

Deferred (logged in deferred-work.md): FQN Library.Keyword disambiguation,
multi-line [Tags]/[Setup] continuation (pre-existing), template-cell-equals-
control-marker (RF-ambiguous).

Tests: 799 frontend vitest + 17 round-trip/edge green; 16 backend keyword
pytest green; 7 flow-editor e2e green (incl. AC-B3 regression); vue-tsc clean;
ruff below baseline on touched files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…editor retro

document-project pass focused on the Flow Editor epic:
- CLAUDE.md gotchas: robotTextIO round-trip contract + escape helpers + dual
  type-system caveat; FlowEditor deep-watcher activeItemIndex/selection reset
  (suppressFitView) + e2e ≥2-test-case rule; [Template] templateRows model;
  libdoc-per-environment discovery + cache-freshness via packages_changed_at;
  keyword resolution order project>libdoc>bootstrap; ZH locale.
- Flow-editor epic retrospective artifact.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove everything an agent can discover by reading the repo (architecture
tree + LOC, tech-stack list, Makefile command list, API-prefix catalog,
config table, RBAC enum, drift-prone milestone/known-issues/test-gaps lists,
the full release checklist already in the release-publish skill) and the
137-line RTK catalog that duplicates the global CLAUDE.md. Keep the gold:
the non-derivable why-landmine gotchas, a few non-obvious conventions, the
one release trigger landmine + pointer, a compact roadmap, and a BMAD pointer.
332 → ~50 lines.
Old cmd.exe has no VT processing, so uvicorn's colored startup logs render as
raw escapes (←[32mINFO←[0m, Uvicorn running on ←[1m…). Add --no-use-colors to
the generated start-windows.bat in both the offline (build-windows.ps1) and
online (build-online-mac-and-linux.sh) builds. mac/linux keep colors (their
terminals render ANSI; uvicorn auto-disables when piped).
…act cell (EDITOR-9b)

The picker's "Eigener Wert …" previously just appended an empty slot, which the
editor then labelled as the NEXT positional parameter (getArgLabel resolves a
bare slot at index N to the N-th signature param) — so "custom value" silently
became "next parameter", with no way to enter a free value or an arbitrary
name=value.

Now it's a real free-form input: type a bare value (`5s`, `${VAR}`) or a
`name=value` (incl. **kwargs) and it's pushed verbatim — a name=value then
resolves to its named spec regardless of index; a bare value is positional.
The named-parameter list (target a specific param out of order) is unchanged.

Also fixes a real popover-positioning bug surfaced while testing: the
Teleported add-arg popover is pinned to the trigger (which sits at the right
edge / low in the tall detail panel) and could open off-screen on a narrow or
short window. Now clamped to the viewport horizontally and flipped above the
trigger when it would overflow the bottom.

Tests: +3 unit (custom-value contract: verbatim push, name=value resolves to
named spec, empty ignored); e2e flow-editor-add-arg (type name=value → round-
trips to code); i18n EN/DE/FR/ES. 802 vitest green, vue-tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@raffelino
raffelino merged commit e8728d6 into main Jun 15, 2026
13 checks passed
@raffelino
raffelino deleted the chore/demo-readiness-bmad branch June 15, 2026 08:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant