Skip to content

Release 0.14.0 - #63

Merged
raffelino merged 22 commits into
mainfrom
release-0.14.0
Sep 28, 2026
Merged

raffelino merged 22 commits into
mainfrom
release-0.14.0

Conversation

@raffelino

Copy link
Copy Markdown
Collaborator

Release 0.14.0, stacked on the still-open 0.13.0 release (#62). Merge #62 first, or merge this one instead since it contains 0.13.0.

What's in it

  • Environments on RoboScope's own Python, or an imported venv (Admin-only; never auto-created or deleted from disk; uninstall blocked on RoboScope's own interpreter).
  • Epic V14, "Close the silent gaps" (from a BMAD gap brainstorm, see _bmad-output/planning-artifacts/v014-epics.md):
    • V14.1 Scheduled runs actually fire, plus a "Run now" button and last/next-run tracking.
    • V14.2 Environment variables can be edited and deleted, and are injected into runs (%{NAME}), with a deny-list for names that hijack the interpreter.
    • V14.3 Report results export as CSV/JSON, with formula-injection neutralisation.
    • V14.4 Delete a single report, repo-scoped Editor.
    • V14.5 Per-file diff preview in the Publish dialog.
  • Security fix: listing environment variables could overwrite stored secrets with ********.
  • Dependency upgrade: Vite 8, Vitest 5, vue-tsc 3, vue-i18n 11, Pinia 4, Vue Router 5, RF 7.5, FastAPI 0.141, cryptography 50, Playwright 1.63. Node 24 replaces EOL Node 20 in CI and Docker.

Verification (local, macOS arm64)

  • Backend: 2445 passed. Integration: 39 passed, 4 skipped.
  • Frontend: 918 vitest passed, vue-tsc clean, vite build OK.
  • E2E: 343 passed, 6 skipped. browser-library failed and was fixed; flow-editor-template:82 failed once in the full run but passes in isolation (flaky).
  • Offline macOS arm64 ZIP built, installed and started: /health reports 0.14.0. The own-Python environment was exercised against the installed build.
  • Not verified locally: the Docker image build (no Docker runtime on this machine) and Windows. Both are covered by the CI gates on this PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GReqJWK77kV77FkNvsLgrL

raffelino and others added 22 commits July 31, 2026 12:01
Adding a keyword from a repo resource that sits in the SAME directory as
the open file asked the user to pip-install it.

`resourceImportPath` returns a bare basename for a same-directory file
(e.g. `09_database.robot`), and `addLibrary` classified imports with
`endsWith('.resource') || includes('/')`. A `.robot` sibling matched
neither, so it was written as `Library    09_database.robot` (invalid RF)
and `skipInstallCheck` stayed false, which surfaced the pip-install
dialog via `libraries-changed` -> `onLibrariesChanged`.

Extract the classification as `isResourceImport()` in the pure
`resourcePath.ts` module and cover every RF resource-file extension
(.robot/.resource/.txt/.tsv, case-insensitive). Both symptoms derive
from the same flag, so both are fixed at the source.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Clicking a keyword lifts it into the add-bar above the tree; that bar now
shows its documentation and source right there, so choosing the right
keyword no longer requires opening the doc modal first.

Project keywords had no doc to show at all: the repo parser extracted
[Arguments] but not [Documentation], and they never go through libdoc, so
`explorer.keywords` has no entry for them. Three gaps closed:

- backend `parse_robot_keywords_in_repo` now parses [Documentation],
  joining row cells with a space and `...` continuation rows with a
  newline (RF semantics), using a real 2+-space/tab cell separator
- `getKeywordInfo` consults `projectKeywords` first, mirroring the arg
  precedence (repo keywords shadow libraries) and reporting the source
  file as the origin
- the palette renders a text preview via the new pure `docPreviewText`
  helper; libdoc HTML is flattened (block tags -> newlines) rather than
  rendered, since the column is narrow and the doc modal already exists

BuiltIn docs are lazy-fetched on selection and memoized, guarded by a
token against out-of-order resolution and by an exact-name check so
`fetchKeywordInfo`'s first-hit fallback can't caption a keyword with an
unrelated one's docs. Reuses the existing docModal.loading/noDoc strings,
so no new i18n keys.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes #58.

The explorer detected sections with `line.lower().startswith("*** keyword")`,
which matches exactly one of the spellings RF accepts. Every other valid form
fell through to the generic `startswith("***")` branch — which CLOSED the
section instead of opening it. A file that parses and runs perfectly in RF
therefore showed an empty keyword palette.

The same check backed four surfaces, so they all went blank together: keyword
discovery, test-case listing, tag discovery and the file tree's test counts.

Now recognised, each verified against RF 7.4.2 rather than assumed:

    ***Keywords***   **** Keywords ****   * Keywords   *** Keywords
    *** Keyword ***  (singular)           a leading UTF-8 BOM
    *** 关键字 ***    in a file declaring `Language:`

The BOM case is the likely trigger behind the report: it only ever lands on a
file's first line, which for a `.resource` file is its `*** Keywords ***`
header — so a single invisible byte hid every keyword in the file.

Still rejected, because RF rejects them too: tab-padded headers, and translated
headers with no `Language:` declaration. Being more permissive than RF would be
its own bug — the explorer would advertise keywords RF cannot resolve at run
time.

Header recognition moves into `explorer/rf_sections.py`, shared by all four
call sites. Translated names come from `robot.conf.languages` (a hard backend
dependency already) rather than a table maintained here, so new RF languages
are picked up for free. `*** Tasks ***` now counts towards a file's test count.

The editor's own parser had the same blind spot and rendered such files as an
empty Flow Editor. Verified before changing it that content always survived the
round-trip as preamble, so this was never data loss. Translated headers stay
backend-only there — that needs RF's translation table, which the browser lacks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Minor bump, not a patch: this cycle adds inline keyword documentation to the
Flow Editor palette. 0.12.1 is already published.

Also documents the palette's inline documentation in the in-app docs
(EN/DE/FR/ES). README and the GitHub Pages landing page describe the palette
only in general terms and needed no change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- frontend: vite 8, vitest 5, vue-tsc 3, TypeScript 5.9, vue-i18n 11,
  pinia 4, vue-router 5, js-yaml 5, jsdom 30; drop unused tsconfig presets
- backend: uv lock --upgrade (RF 7.5, fastapi 0.141, cryptography 50,
  reportlab 5, rf-mcp 0.35, fastmcp 3.4); raise security-relevant floors
- e2e/extension: Playwright 1.63 (+ matching Docker images), mocha 12,
  chai 6, eslint 10; npm audit clean
- CI + frontend Docker image: Node 20 (EOL) -> Node 24 LTS
- fix vue-tsc 3 TS2589/index typing in FlowEditor and StatsView

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GReqJWK77kV77FkNvsLgrL
New venv_mode on environment creation: managed (default), system
(interpreter RoboScope runs in) and existing (import a venv path).
Non-managed modes are ADMIN-only, validated by probing the interpreter,
never auto-created and never deleted from disk. Uninstalling packages
from RoboScope's own interpreter is refused (409).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GReqJWK77kV77FkNvsLgrL
GET /repos/{id}/diff?path= returns {path, status, diff, truncated}: the
unified diff of one working-tree file against HEAD. Untracked files show
as all-added, deleted as all-removed, binaries as diff=null. Output is
capped at 200 KB. A strict path guard rejects absolute paths, '..',
directories and anything that resolves outside the repo root (symlinks
included) with 400. Pathspecs are literal (no globs/magic). Local repos
get 409. Read access is the same as GET /status.

The PublishModal gets a lazy "Show changes" toggle on each file row. The
diff renders as escaped text in a <pre> with +/- lines coloured through
main.css vars. There is no v-html and no new dependency, and toggling
leaves the checkbox selection alone. i18n in EN/DE/FR/ES/ZH, plus
in-app docs in all four doc locales.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GReqJWK77kV77FkNvsLgrL
- PATCH/DELETE /environments/{id}/variables/{var_id} (EDITOR). Keys are
  validated as env-var names; a case-insensitive deny-list rejects
  interpreter-hijack names (PATH, VIRTUAL_ENV, PYTHONPATH, PYTHONHOME,
  PYTHONSTARTUP, PYTHONUSERBASE, LD_PRELOAD, DYLD_*, NODE_OPTIONS, ...).
  Duplicate keys return 409. Secrets are re-encrypted on update; an empty
  value keeps the stored secret.
- Fix: GET /variables masked secrets by mutating the ORM row inside a
  committing session, which overwrote stored secrets with "********".
  Responses are now built without touching the row, and POST masks too.
- resolve_env_vars() decrypts secrets only at injection time. Subprocess
  runner merges them into the child env before PATH/VIRTUAL_ENV (those
  still win); docker runner merges them before ROBOT_* run variables
  (run variables win). Suites read them as %{NAME}.
- EnvironmentsView: add / inline edit / delete (confirm), secret values
  as password input with an "(unchanged)" placeholder. i18n EN/DE/FR/ES,
  docs updated in all four locales.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GReqJWK77kV77FkNvsLgrL
- Cron validated on schedule create/update (CronTrigger.from_crontab, 422
  on invalid); next_run_at maintained as naive UTC (server-local cron).
- Per-minute APScheduler heartbeat run_due_schedules(): fires due active
  schedules once (missed slots coalesce), skips when the previous run is
  still pending/running, or the creator is inactive/deleted, or the repo
  is gone. Runs as the schedule's creator; never copies variables or
  advanced config (gate tripwire stays green).
- POST /schedules/{id}/run (RUNNER+ effective role on the repo) starts a
  run immediately without moving next_run_at.
- Deleting a schedule detaches its runs instead of violating the FK.
- Schedules table: Last run / Next run columns and a Run now button that
  switches to the Runs tab and highlights the new run. i18n EN/DE/FR/ES,
  in-app docs subsection on scheduling semantics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GReqJWK77kV77FkNvsLgrL
V14.3: GET /reports/{id}/export?format=csv|json. One row per TestResult
(suite_name, test_name, long_name, status, duration_seconds, tags,
start_time, end_time, error_message) via stdlib csv; cells starting with
= + - @ TAB CR are prefixed with ' (CSV injection). Bad format -> 422,
missing report -> 404. Export CSV / Export JSON buttons on the report
detail view and the run detail panel.

V14.4: DELETE /reports/{id} (204/404), gated by
require_effective_role_for_report(EDITOR); reports without a resolvable
repo (uploads, orphaned runs) fall back to the global role floor.
reports/service.py::delete_report is the single per-report helper and
DELETE /reports/all now loops over it. It only rmtrees the report's own
top-level dir under REPORTS_DIR (run_*/ or archives/<name>/), never the
root, archives/ or anything outside; ai_jobs.report_id is detached.
The linked ExecutionRun is kept (its report link resolves to null).

i18n EN/DE/FR/ES, in-app docs updated, pytest + vitest + one e2e spec.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GReqJWK77kV77FkNvsLgrL
Minor bump: Epic V14 closes silent gaps found in a BMAD gap brainstorm
(schedules now fire, environment variables reach runs, report CSV/JSON
export, single-report delete, publish diff preview), adds environments
backed by RoboScope's own Python or an imported venv, and upgrades all
dependencies (Vite 8, Vitest 5, vue-i18n 11, Pinia 4, Vue Router 5,
RF 7.5, Playwright 1.63, Node 24 in CI).

Also fixes a venv-path line in the environment details that showed for
API responses without venv_kind (caught by browser-library e2e).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GReqJWK77kV77FkNvsLgrL
CI runners have no git identity, so 'git commit' exited 128 in Gate 5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GReqJWK77kV77FkNvsLgrL
…ces it

SQLite does not enforce foreign keys, so deleting a repository left its
runs, reports, schedules, recordings and KPI rows orphaned (then neither
deletable nor cancellable, even for ADMIN); PostgreSQL returned a 500.
delete_repository now removes runs via delete_report (report files too)
and clears every other repositories.id reference found in the metadata.
It is refused with 409 while a run is pending or running. The confirm
dialog and docs (EN/DE/FR/ES) now say what gets deleted.

Open since the 0.12.1 review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GReqJWK77kV77FkNvsLgrL
sync_repository returns git's error as a string, which sync_repo
ignored, so a pull blocked by an uncommitted in-app edit still showed a
green 'synced' state. The error now sets sync_status='error' with git's
message (no retry: the failure is deterministic).

Core of the draft PR #46 without its unrelated reformatting.

Fixes #36

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GReqJWK77kV77FkNvsLgrL
…rules (#58)

The 0.13.0 fix for #58 covered explorer/service.py, but the visual
editor's keyword search goes through ai/rf_knowledge._scan_repo_files,
a second parser with the old header check and a non-BOM-safe read. A
BOM-prefixed resource (typical on Windows) or translated headers
(*** 关键字 ***) yielded no keywords there. It now reuses rf_sections and
reads utf-8-sig.

Also: a single non-ASCII character starts a keyword search (one CJK
character is a real query), and keyword file paths are returned in
POSIX form so the palette groups them correctly on Windows.

Refs #58

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GReqJWK77kV77FkNvsLgrL
@raffelino
raffelino marked this pull request as ready for review September 25, 2026 20:29
@raffelino
raffelino merged commit 12458a4 into main Sep 28, 2026
9 checks passed
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