]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/log
e-mobility-charging-stations-simulator.git
9 hours agofix(ui/web): smooth icon-btn danger hover shadow and remove dead token main
Jérôme Benoit [Fri, 8 May 2026 18:45:44 +0000 (20:45 +0200)] 
fix(ui/web): smooth icon-btn danger hover shadow and remove dead token

- Add box-shadow to .modern-icon-btn transition shorthand so the danger
  variant hover glow animates instead of snapping
- Remove --skin-shadow-color (declared line 61 but never consumed)

10 hours agofix(ui/web): fix editable pill hover visibility in light themes
Jérôme Benoit [Fri, 8 May 2026 18:30:30 +0000 (20:30 +0200)] 
fix(ui/web): fix editable pill hover visibility in light themes

Refactor pill background to use scoped --_pill-bg and --_pill-bg-hover
custom properties (MD3 pattern). Each variant co-locates its base and
hover background values, and light-mode overrides reassign both.

The hover rule simply consumes var(--_pill-bg-hover), eliminating the
specificity battle that prevented the hover effect from appearing in
light themes.

10 hours agorefactor(ui/web): remove redundant connector status column from classic skin
Jérôme Benoit [Fri, 8 May 2026 18:00:49 +0000 (20:00 +0200)] 
refactor(ui/web): remove redundant connector status column from classic skin

The connector status is now accessible via the Set Status dropdown in
the Actions column, making the dedicated read-only Status column
redundant. Move status/error-code selects to the top of Actions and
style them to fill the column width consistently with buttons.

13 hours agofix(sandcastle): patch pi thinking option and replace type indirections
Jérôme Benoit [Fri, 8 May 2026 15:08:22 +0000 (17:08 +0200)] 
fix(sandcastle): patch pi thinking option and replace type indirections

Apply pnpm patch for @ai-hero/sandcastle porting PR #584 (pi --thinking
flag). Wire the thinking option through agentProvider() and replace
Awaited<ReturnType<...>> indirections with direct type imports (Sandbox,
SandboxRunResult, RunResult, PiOptions).

14 hours agofix(sandcastle): wire reasoning effort through to agent providers
Jérôme Benoit [Fri, 8 May 2026 14:09:44 +0000 (16:09 +0200)] 
fix(sandcastle): wire reasoning effort through to agent providers

Pass AGENT_*_EFFORT constants through agentProvider() to the opencode
provider's variant flag. LoopStrategy gains actorEffort/criticEffort
optional overrides following the same pattern as actorModel/criticModel.

- opencode: effort mapped to --variant CLI flag
- pi: no effort support (provider limitation, param silently ignored)

14 hours agofix(deps): update all non-major dependencies (#1840)
renovate[bot] [Fri, 8 May 2026 13:49:01 +0000 (15:49 +0200)] 
fix(deps): update all non-major dependencies (#1840)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
14 hours agochore(deps): update dependency mypy to v2 (#1841)
renovate[bot] [Fri, 8 May 2026 13:42:44 +0000 (15:42 +0200)] 
chore(deps): update dependency mypy to v2 (#1841)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
15 hours agofix(sandcastle): update pi package to @earendil-works/pi-coding-agent
Jérôme Benoit [Fri, 8 May 2026 12:43:21 +0000 (14:43 +0200)] 
fix(sandcastle): update pi package to @earendil-works/pi-coding-agent

The pi coding agent npm package migrated from @mariozechner/pi-coding-agent
to @earendil-works/pi-coding-agent (repo moved to earendil-works/pi-mono).

28 hours agochore: release main (#1833) cli@v4.7.0 ocpp-server@v4.7.0 simulator@v4.7.0 ui-common@v4.7.0 v4 v4.7 web@v4.7.0
Jérôme Benoit [Fri, 8 May 2026 00:04:29 +0000 (02:04 +0200)] 
chore: release main (#1833)

* chore: release main

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
28 hours agofix(sandcastle): add missing JSDoc descriptions to satisfy lint rules
Jérôme Benoit [Thu, 7 May 2026 23:54:12 +0000 (01:54 +0200)] 
fix(sandcastle): add missing JSDoc descriptions to satisfy lint rules

28 hours agorefactor(sandcastle): remove plannerOutput from TaskSpec
Jérôme Benoit [Thu, 7 May 2026 23:48:28 +0000 (01:48 +0200)] 
refactor(sandcastle): remove plannerOutput from TaskSpec

Raw agent stdout will be handled by sandcastle's own debug/logging
mechanism rather than stored in-memory on TaskSpec. Structured fields
(acceptanceCriteria, rootCauseHypothesis, confidence, issueType) remain
as the sole inter-agent communication channel.

29 hours agorefactor(sandcastle): remove redundant lastFindings from LoopResult
Jérôme Benoit [Thu, 7 May 2026 23:33:21 +0000 (01:33 +0200)] 
refactor(sandcastle): remove redundant lastFindings from LoopResult

Derive last-round findings from roundHistory.at(-1)?.findings in
finalizer.ts instead of maintaining a separate field. The PR body
now shows all critic findings from the final round (including LOW
confidence) for full transparency.

29 hours agofix(sandcastle): assign opus to actor, sonnet to planner, increase planner iterations
Jérôme Benoit [Thu, 7 May 2026 23:20:58 +0000 (01:20 +0200)] 
fix(sandcastle): assign opus to actor, sonnet to planner, increase planner iterations

Swap model assignments: claude-opus-4.6 (high effort) for the actor,
claude-sonnet-4.6 (medium effort) for the planner. Increase planner
maxIterations from 1 to 5 so it can actually read AGENTS.md and
project context before producing its analysis.

29 hours agofeat(sandcastle): enrich planner with acceptance criteria and root cause hypothesis
Jérôme Benoit [Thu, 7 May 2026 23:04:27 +0000 (01:04 +0200)] 
feat(sandcastle): enrich planner with acceptance criteria and root cause hypothesis

The planner now produces structured analysis per issue: issueType,
confidence, rootCauseHypothesis, and acceptanceCriteria. These flow
into the actor prompt (confidence-gated hypothesis + criteria) and the
critic prompt (criteria as verification checklist).

- Confidence controls plan specificity: high → full context, medium/low → criteria only
- All planner-generated fields are sanitized and length-bounded
- Critic evaluates observable outcomes, never plan adherence
- Backward-compatible: missing fields result in empty template variables

29 hours agofeat(sandcastle): add roundHistory to LoopResult and plannerOutput to TaskSpec
Jérôme Benoit [Thu, 7 May 2026 22:36:27 +0000 (00:36 +0200)] 
feat(sandcastle): add roundHistory to LoopResult and plannerOutput to TaskSpec

Replace the unused onRoundComplete callback with a structured
roundHistory array that accumulates RoundSnapshot per round
(including post-loop validation retry). Attach raw planner stdout
to TaskSpec.plannerOutput for downstream verification use.

This enables a future planner-verification step to receive the full
findings history alongside the original plan context.

30 hours agofix(ui): allow changing status of individual connectors (#1834)
github-actions[bot] [Thu, 7 May 2026 21:56:02 +0000 (23:56 +0200)] 
fix(ui): allow changing status of individual connectors (#1834)

* feat(ui/web): allow changing status of individual connectors

Add a 'Set Status' action to the connector UI in both modern and classic
skins. Users can simulate OCPP connector statuses (e.g. Faulted, Unavailable)
directly from the dashboard.

- UIClient.setConnectorStatus sends STATUS_NOTIFICATION via existing ProcedureName
- useConnectorActions exposes setConnectorStatus with pending.setStatus guard
- Modern skin: SetConnectorStatusDialog presents a status picker in a modal
- Classic skin: inline <select> triggers status change on change event
- Test helpers and composable tests updated accordingly

* fix: correct OCPP version handling for connector status changes

- Add OCPP20ConnectorStatusEnumType enum to ui-common
- Fix UIClient.setConnectorStatus to send version-aware payload:
  connectorStatus field for OCPP 2.0.x, status field for OCPP 1.6
- Update useConnectorActions to accept ocppVersion and pass it through;
  widen status parameter type to union of 1.6 and 2.0.x enums;
  accept optional onSuccess callback per action invocation
- Fix SetConnectorStatusDialog to close only after action resolves
  (pass close as onSuccess instead of calling it immediately)
- Show version-appropriate status options in SetConnectorStatusDialog
- Forward ocppVersion and onRefresh from ConnectorRow to dialog
- Propagate need-refresh event through ConnectorRow → StationCard → ModernLayout
- Add tests for new OCPP 2.0.x paths and dialog behaviour

* [autofix.ci] apply automated fixes

* feat(ui): complete connector status change with error simulation and local state update

- Fix classic skin to show version-appropriate status options (OCPP 1.6/2.0.x)
- Pass ocppVersion to useConnectorActions in classic skin
- Replace passthrough STATUS_NOTIFICATION handler with sendAndSetConnectorStatus
  to update in-memory connector state and emit connectorStatusChanged event
- Add OCPP 1.6 errorCode support (issue requests error simulation capability)
- Add OCPP16ChargePointErrorCode enum to ui-common
- Add error code selector in both skins (OCPP 1.6 only, hidden for 2.0.x)
- Remove unnecessary re-exports from useConnectorActions composable
- Update tests for new errorCode parameter and passthrough behavior change

Resolves the outstanding HIGH findings from automated review.

* fix(ui): polish connector status — reactivity consistency, JSDoc, tests, init from current status

- Wrap props in computed() in SetConnectorStatusDialog for consistency
- Fix JSDoc on mountDialog test helper to satisfy jsdoc/require-jsdoc
- Initialize selectedStatus from connector.status for OCPP 2.0.x too
- Add 5 unit tests for classic skin CSConnector status change behavior

* refactor(ui): remove redundant onRefresh from connector actions

The server already pushes a REFRESH notification via WebSocket when
connector state changes (connectorStatusChanged → buildUpdatedMessage →
workerEventUpdated → scheduleClientNotification → REFRESH broadcast).

The onRefresh callback in useConnectorActions duplicated this by manually
calling getChargingStations() after each action. Remove it along with
the need-refresh event bubbling chain in the modern skin.

The useAsyncAction onRefresh mechanism remains available for composables
where the server does NOT push updates (e.g., useStationActions).

* [autofix.ci] apply automated fixes

* refactor(ui-common): extract buildStatusNotificationPayload shared builder

Factor version-aware StatusNotification payload construction into
ui-common alongside existing buildAuthorize/Start/StopTransactionPayload
builders. Both CLI and Web UI now use the shared builder, eliminating
duplicated OCPP 1.6 vs 2.0.x branching logic.

* [autofix.ci] apply automated fixes

* fix(ui-common): keep buildStatusNotificationPayload strongly typed

Remove string fallback from status/errorCode parameters — the builder
accepts only the OCPP enum types. The CLI casts user input at the call
site, keeping the shared API type-safe.

* [autofix.ci] apply automated fixes

* fix(ui-common): remove invalid Occupied from OCPP16ChargePointStatus enum

Occupied is an OCPP 2.0.x-only connector status. It was erroneously
included in the OCPP 1.6 enum, causing the UI dropdown to offer an
invalid status option for OCPP 1.6 stations. Tests referencing it are
updated to use OCPP20ConnectorStatusEnumType.OCCUPIED or a valid 1.6
status as appropriate.

* fix(ui-common): make ChargePointStatus a union of OCPP 1.6 and 2.0.x enums

ChargePointStatus was aliased to OCPP16ChargePointStatus only, which
made ConnectorStatus.status unable to represent OCPP 2.0.x values like
Occupied. Now it is OCPP16ChargePointStatus | OCPP20ConnectorStatusEnumType,
matching the src/ canonical ConnectorStatusEnum pattern.

* refactor(ui-common): use ChargePointStatus type alias instead of inline union

Replace all occurrences of the verbose
'OCPP16ChargePointStatus | OCPP20ConnectorStatusEnumType' inline union
with the existing ChargePointStatus type alias across ui-common, web UI,
and CLI.

* [autofix.ci] apply automated fixes

* fix: add connectorId guard to handleStatusNotification for consistency

Other broadcast channel handlers (handleMeterValues, UNLOCK_CONNECTOR,
LOCK_CONNECTOR) throw BaseError when connectorId is missing. Without
this guard, a malformed request would silently succeed.

* fix(ui): address review feedback — clickable status pill, stale state sync, error-code apply

Modern skin:
- Replace 'Set Status' button with clickable status pill (edit icon on
  hover, tooltip with status, aria-haspopup=dialog). More compact UX per
  reviewer request (DerGenaue).
- Add .modern-pill--editable CSS with hover border, focus ring, and
  fade-in edit icon.

Classic skin:
- Add watch on props.connector.status to sync selectedStatus ref when
  server pushes new state (fixes stale dropdown after external changes).
- Add @change handler on error-code select so changing errorCode alone
  also triggers a StatusNotification (previously only status change did).

Addresses review feedback from hyperspace-insights, copilot, and
DerGenaue.

* fix: persist errorCode on ConnectorStatus, remove Partial<> cast, add tooltip

Backend:
- Add errorCode field to ConnectorStatus (src/ and ui-common)
- Persist errorCode in sendAndSetConnectorStatus alongside status
- Move errorCode defaulting from buildStatusNotificationRequest to
  OCPP16RequestService.buildRequestPayload via spread default pattern:
  { errorCode: NO_ERROR, ...commandParams }
- Remove Partial<> cast and ?? fallback from the builder — it now
  simply passes through commandParams.errorCode (always defined by
  the time it reaches the builder)

UI:
- Status pill tooltip shows errorCode when present and not NoError
  (e.g., 'Faulted (ConnectorLockFailure)'), per DerGenaue's request
- ConnectorStatus.errorCode propagates automatically through existing
  buildUpdatedMessage → spread serialization chain

* fix(ui): address DerGenaue feedback — always-visible icon, preserve pill height

- Edit icon always visible (no hover-only opacity transition)
- Smaller icon (8px) + reduced gap (3px) to preserve original pill height
- Simpler pencil SVG (single path, thicker stroke for clarity at small size)
- Removed opacity animation rules

* fix(ui): editable status pill design (#1838)

* fix: validate status field in handleStatusNotification

Prevents connectorStatus.status corruption when ocppStrictCompliance is
disabled and the payload arrives without a status value.

---------

Co-authored-by: OpenCode Agent <agent@opencode.ai>
Co-authored-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jérôme Benoit <jerome.benoit@sap.com>
Co-authored-by: Daniel <7558512+DerGenaue@users.noreply.github.com>
30 hours agofix(web): fetch templates on dialog open via layout composable (#1837) (#1839)
github-actions[bot] [Thu, 7 May 2026 21:33:27 +0000 (23:33 +0200)] 
fix(web): fetch templates on dialog open via layout composable (#1837) (#1839)

Expose getTemplates from useLayoutData instead of duplicating the fetch
in useAddStationsForm. Each layout triggers the refresh at the right
moment:

- Modern skin: watch showAddDialog → getTemplates() on open
- Classic skin: watch route → getTemplates() on ADD_CHARGING_STATIONS

The templatesEqual guard in useAddStationsForm prevents resetting the
user's selection when the refetch returns an identical list.

Co-authored-by: Jérôme Benoit <jerome.benoit@sap.com>
31 hours agofix(sandcastle): run validation across all workspace packages
Jérôme Benoit [Thu, 7 May 2026 20:43:24 +0000 (22:43 +0200)] 
fix(sandcastle): run validation across all workspace packages

31 hours agofix(sandcastle): increase validation timeout to 600s
Jérôme Benoit [Thu, 7 May 2026 20:40:15 +0000 (22:40 +0200)] 
fix(sandcastle): increase validation timeout to 600s

32 hours agofix(sandcastle): add configurable agent provider (pi/opencode)
Jérôme Benoit [Thu, 7 May 2026 19:33:28 +0000 (21:33 +0200)] 
fix(sandcastle): add configurable agent provider (pi/opencode)

Introduce AGENT_PROVIDER constant to switch between pi and opencode backends.
pi streams JSON output immediately, avoiding the opencode idle timeout bug
where git check-ignore indexing produces zero stdout.

Also removes redundant copyToWorktree since onSandboxReady pnpm install
handles node_modules via the mounted pnpm store.

34 hours agofix(sandcastle): revert idle timeout to 300s and remove redundant copyToWorktree
Jérôme Benoit [Thu, 7 May 2026 18:26:40 +0000 (20:26 +0200)] 
fix(sandcastle): revert idle timeout to 300s and remove redundant copyToWorktree

The 600s timeout was a misguided workaround for serena MCP init. The actual
root cause is an opencode bug: zero stdout during its git check-ignore
indexing phase. Removing copyToWorktree since pnpm install in onSandboxReady
already handles node_modules via the mounted pnpm store.

35 hours agochore(sandcastle): increase idle timeout to 600s for MCP init
Jérôme Benoit [Thu, 7 May 2026 17:00:49 +0000 (19:00 +0200)] 
chore(sandcastle): increase idle timeout to 600s for MCP init

35 hours agoci(sandcastle): schedule daily run at midnight Europe/Paris
Jérôme Benoit [Thu, 7 May 2026 16:46:03 +0000 (18:46 +0200)] 
ci(sandcastle): schedule daily run at midnight Europe/Paris

36 hours agochore(deps): update dependency lint-staged to v17 (#1836)
renovate[bot] [Thu, 7 May 2026 16:23:11 +0000 (18:23 +0200)] 
chore(deps): update dependency lint-staged to v17 (#1836)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
41 hours agochore(sandcastle): set planner effort to high
Jérôme Benoit [Thu, 7 May 2026 10:40:16 +0000 (12:40 +0200)] 
chore(sandcastle): set planner effort to high

41 hours agochore(sandcastle): add agent effort constants (future use)
Jérôme Benoit [Thu, 7 May 2026 10:39:27 +0000 (12:39 +0200)] 
chore(sandcastle): add agent effort constants (future use)

43 hours agochore(deps): update all non-major dependencies (#1835)
renovate[bot] [Thu, 7 May 2026 09:24:47 +0000 (11:24 +0200)] 
chore(deps): update all non-major dependencies (#1835)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
43 hours agochore(sandcastle): clarify prompt headings with role taxonomy
Jérôme Benoit [Thu, 7 May 2026 09:20:30 +0000 (11:20 +0200)] 
chore(sandcastle): clarify prompt headings with role taxonomy

43 hours agorefactor(sandcastle): rename implement-prompt.md to actor-prompt.md
Jérôme Benoit [Thu, 7 May 2026 09:17:22 +0000 (11:17 +0200)] 
refactor(sandcastle): rename implement-prompt.md to actor-prompt.md

43 hours agodocs(sandcastle): fix stale JSDoc referencing empty string sentinel
Jérôme Benoit [Thu, 7 May 2026 08:57:20 +0000 (10:57 +0200)] 
docs(sandcastle): fix stale JSDoc referencing empty string sentinel

43 hours agorefactor(sandcastle): add error observability and type-safe sentinels
Jérôme Benoit [Thu, 7 May 2026 08:53:45 +0000 (10:53 +0200)] 
refactor(sandcastle): add error observability and type-safe sentinels

- Add failureReason to LoopResult for post-mortem debugging
- Replace captureHeadSha sentinel '' with null (type-safe)
- discover() throws on planner failure (no hidden process.exitCode)
- Add console.debug in hashContextLines for dedup diagnostics

43 hours agorefactor(sandcastle): improve separation of concerns and API clarity
Jérôme Benoit [Thu, 7 May 2026 08:43:02 +0000 (10:43 +0200)] 
refactor(sandcastle): improve separation of concerns and API clarity

- Extract runValidation into validation.ts
- Encapsulate nonce in loop (remove from strategy interface)
- Delete dead GRACE_TIMEOUT_MS constant
- Reorganize constants into proper domain groups
- Un-export FindingSchema and extractStderr

44 hours agorefactor(sandcastle): generalize actor/critic loop with LoopContext and coherent API
Jérôme Benoit [Thu, 7 May 2026 08:25:26 +0000 (10:25 +0200)] 
refactor(sandcastle): generalize actor/critic loop with LoopContext and coherent API

- Introduce LoopContext to group invariant params (spec, sandbox, strategy, baseBranch, signal)
- Extract GIT_BASE_BRANCH constant, thread through loop + finalization + prompts
- Rename 'implementer' to 'actor' in generic loop code
- Add extensibility: validate?, actorModel?, criticModel? on LoopStrategy
- Add baseBranch to RefinementLoopOptions and LoopResult
- Make Finding.category extensible (z.string())
- Make TaskSpec.labels optional
- Fix param ordering: pushBranch(spec, cwd, ...), deduplicateFindings(findings, cwd, ...)
- Simplify root lint-staged to cover all TS files

45 hours agochore: simplify root lint-staged config to cover all TS files
Jérôme Benoit [Thu, 7 May 2026 07:24:19 +0000 (09:24 +0200)] 
chore: simplify root lint-staged config to cover all TS files

45 hours agorefactor(sandcastle): remove StrategyConfig type alias, inline intersection
Jérôme Benoit [Thu, 7 May 2026 07:13:10 +0000 (09:13 +0200)] 
refactor(sandcastle): remove StrategyConfig type alias, inline intersection

45 hours ago[autofix.ci] apply automated fixes
autofix-ci[bot] [Thu, 7 May 2026 06:57:35 +0000 (06:57 +0000)] 
[autofix.ci] apply automated fixes

45 hours agorefactor(sandcastle): organize constants by domain with coherent naming
Jérôme Benoit [Thu, 7 May 2026 06:49:45 +0000 (08:49 +0200)] 
refactor(sandcastle): organize constants by domain with coherent naming

45 hours ago[autofix.ci] apply automated fixes
autofix-ci[bot] [Thu, 7 May 2026 06:45:27 +0000 (06:45 +0000)] 
[autofix.ci] apply automated fixes

45 hours agochore(sandcastle): use gpt-5.4 as critic model
Jérôme Benoit [Thu, 7 May 2026 06:42:17 +0000 (08:42 +0200)] 
chore(sandcastle): use gpt-5.4 as critic model

45 hours agorefactor(sandcastle): split AGENT_MODEL into per-role constants
Jérôme Benoit [Thu, 7 May 2026 06:40:37 +0000 (08:40 +0200)] 
refactor(sandcastle): split AGENT_MODEL into per-role constants

2 days agofix(sandcastle): improve PR metadata accuracy
Jérôme Benoit [Wed, 6 May 2026 23:57:51 +0000 (01:57 +0200)] 
fix(sandcastle): improve PR metadata accuracy

2 days agochore(sandcastle): install uv, harden APT setup, add memories to prompts
Jérôme Benoit [Wed, 6 May 2026 23:18:06 +0000 (01:18 +0200)] 
chore(sandcastle): install uv, harden APT setup, add memories to prompts

2 days agochore(sandcastle): install uv, harden APT repo setup
Jérôme Benoit [Wed, 6 May 2026 22:44:33 +0000 (00:44 +0200)] 
chore(sandcastle): install uv, harden APT repo setup

- Add uv via griffo.io APT (provides uvx for MCP servers in sandbox)
- Eliminate pipe patterns to prevent silent download failures
- Migrate GitHub CLI key to /etc/apt/keyrings/
- Remove gpg from base deps (no longer needed)

2 days agochore(sandcastle): install uv via griffo.io APT for MCP server support
Jérôme Benoit [Wed, 6 May 2026 22:35:32 +0000 (00:35 +0200)] 
chore(sandcastle): install uv via griffo.io APT for MCP server support

2 days agochore(sandcastle): increase MAX_PARALLEL to 5
Jérôme Benoit [Wed, 6 May 2026 22:24:47 +0000 (00:24 +0200)] 
chore(sandcastle): increase MAX_PARALLEL to 5

2 days agochore(sandcastle): increase MAX_CRITIC_ROUNDS to 10
Jérôme Benoit [Wed, 6 May 2026 21:56:49 +0000 (23:56 +0200)] 
chore(sandcastle): increase MAX_CRITIC_ROUNDS to 10

2 days agochore(sandcastle): remove requirements doc from repo (kept externally)
Jérôme Benoit [Wed, 6 May 2026 19:54:04 +0000 (21:54 +0200)] 
chore(sandcastle): remove requirements doc from repo (kept externally)

2 days agochore(sandcastle): increase timeouts for large repo tasks
Jérôme Benoit [Wed, 6 May 2026 19:30:24 +0000 (21:30 +0200)] 
chore(sandcastle): increase timeouts for large repo tasks

CI job timeout: 60min → 120min
Task timeout: 15min → 100min (allows 5 full critic rounds)
Also adds parallel decomposition requirements doc and logs planner errors.

2 days agofix(sandcastle): pre-create .local/share dirs in Dockerfile
Jérôme Benoit [Wed, 6 May 2026 18:47:38 +0000 (20:47 +0200)] 
fix(sandcastle): pre-create .local/share dirs in Dockerfile

Docker creates intermediate directories as root:root for bind mounts.
Pre-creating /home/agent/.local/share/pnpm/store and opencode with
correct ownership prevents EACCES when opencode writes to its data dir.

2 days agofix(sandcastle): remove corepack prepare from Dockerfile
Jérôme Benoit [Wed, 6 May 2026 18:32:12 +0000 (20:32 +0200)] 
fix(sandcastle): remove corepack prepare from Dockerfile

Align with the working Dockerfile from sap-ai-provider. The container
does not need explicit pnpm setup — corepack in node:24 handles it
on demand when pnpm is invoked.

2 days agofix(sandcastle): log planner error details instead of swallowing
Jérôme Benoit [Wed, 6 May 2026 18:13:14 +0000 (20:13 +0200)] 
fix(sandcastle): log planner error details instead of swallowing

2 days agochore: remove temporary release-as overrides
Jérôme Benoit [Wed, 6 May 2026 17:36:57 +0000 (19:36 +0200)] 
chore: remove temporary release-as overrides

2 days agochore: release main (#1832) cli@v4.6.1 ocpp-server@v4.6.1 simulator@v4.6.1 ui-common@v4.6.1 v4.6 web@v4.6.1
Jérôme Benoit [Wed, 6 May 2026 17:32:34 +0000 (19:32 +0200)] 
chore: release main (#1832)

* chore: release main

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2 days agofix(deps): override ip-address vulnerability
Jérôme Benoit [Wed, 6 May 2026 17:21:54 +0000 (19:21 +0200)] 
fix(deps): override ip-address vulnerability

2 days agochore: force release-as 4.6.1 (temporary, remove after release)
Jérôme Benoit [Wed, 6 May 2026 17:13:04 +0000 (19:13 +0200)] 
chore: force release-as 4.6.1 (temporary, remove after release)

2 days agochore: trigger release-please recalculation
Jérôme Benoit [Wed, 6 May 2026 17:05:55 +0000 (19:05 +0200)] 
chore: trigger release-please recalculation

2 days agoci: add sandcastle autonomous agent pipeline
Jérôme Benoit [Wed, 6 May 2026 15:53:58 +0000 (17:53 +0200)] 
ci: add sandcastle autonomous agent pipeline

Automated issue→PR workflow using Docker-sandboxed AI agents with an
implement↔critic refinement loop. Includes:

- GitHub issue discovery with planner agent
- Concurrent task execution with AbortSignal-based cooperative timeout
- Quality ratchet with content-addressed dedup and rollback
- Configurable strategy (prompts, validation, finalization)

Also simplifies clone-count workflow to use vars.GIST_ID instead of
auto-provisioned secret, and adds cspell words for sandcastle domain.

2 days agochore: update serena project configuration
Jérôme Benoit [Wed, 6 May 2026 13:21:23 +0000 (15:21 +0200)] 
chore: update serena project configuration

2 days agofix(deps): update all non-major dependencies (#1830)
renovate[bot] [Wed, 6 May 2026 12:46:23 +0000 (14:46 +0200)] 
fix(deps): update all non-major dependencies (#1830)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
3 days agofix(deps): update all non-major dependencies (#1825)
renovate[bot] [Tue, 5 May 2026 21:32:59 +0000 (23:32 +0200)] 
fix(deps): update all non-major dependencies (#1825)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
5 days agofix: restore comments lost during rebase conflict resolution
Jérôme Benoit [Sun, 3 May 2026 22:20:13 +0000 (00:20 +0200)] 
fix: restore comments lost during rebase conflict resolution

5 days agochore: rename webui component to web for naming consistency
Jérôme Benoit [Sun, 3 May 2026 22:16:34 +0000 (00:16 +0200)] 
chore: rename webui component to web for naming consistency

Align the web UI package name and identifiers with the directory
structure (ui/web) and the CLI convention (ui/cli → cli). Renames
package.json name, release-please component, Dockerfile pnpm filter,
SonarCloud project key, and screenshot asset filename.

5 days agochore: release main (#1819) cli@v4.6.0 ocpp-server@v4.6.0 simulator@v4.6.0 ui-common@v4.6.0 web@v4.6.0
Jérôme Benoit [Sun, 3 May 2026 15:43:51 +0000 (17:43 +0200)] 
chore: release main (#1819)

* chore: release main

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
6 days agofix: stop nullifying wsConnection prematurely in close/terminate
Jérôme Benoit [Sat, 2 May 2026 21:41:49 +0000 (23:41 +0200)] 
fix: stop nullifying wsConnection prematurely in close/terminate

closeWSConnection() and terminateWSConnection() set wsConnection to null
immediately after calling close()/terminate(), but the 'close' event
fires asynchronously afterward. The onClose handler then emitted an
'updated' event with wsState undefined (since wsConnection was null),
causing the UI to display 'ws unknown' instead of 'ws closed'.

Remove the null assignments — the stale WebSocket reference is harmless
(overwritten by the next openWSConnection call) and allows onClose to
read the correct readyState (CLOSED=3) when emitting state updates.

6 days agofix: do not nullify wsConnection in onError handler
Jérôme Benoit [Sat, 2 May 2026 21:21:24 +0000 (23:21 +0200)] 
fix: do not nullify wsConnection in onError handler

The ws library guarantees that a 'close' event always follows an 'error'
event. Eagerly setting wsConnection to null in onError caused the
subsequent onClose handler to emit an 'updated' event with wsState
undefined, making the UI display 'unknown' instead of 'closed'.

6 days agorefactor(webui): remove redundant optimistic fetch from modern skin
Jérôme Benoit [Sat, 2 May 2026 21:08:50 +0000 (23:08 +0200)] 
refactor(webui): remove redundant optimistic fetch from modern skin

Rely solely on server push (ServerNotification.REFRESH) for data refresh
after user actions, eliminating the duplicate getChargingStations() call
that was triggered via the need-refresh event chain.

6 days agofix(deps): update dependency zod to ^4.4.2 (#1823)
renovate[bot] [Sat, 2 May 2026 12:42:07 +0000 (14:42 +0200)] 
fix(deps): update dependency zod to ^4.4.2 (#1823)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
7 days agofix(deps): update dependency basic-ftp to v6 (#1822)
renovate[bot] [Fri, 1 May 2026 22:43:36 +0000 (00:43 +0200)] 
fix(deps): update dependency basic-ftp to v6 (#1822)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
7 days agochore(deps): update sonarsource/sonarqube-scan-action action to v8 (#1821)
renovate[bot] [Fri, 1 May 2026 22:31:10 +0000 (00:31 +0200)] 
chore(deps): update sonarsource/sonarqube-scan-action action to v8 (#1821)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
8 days agochore: remove stale opencode agent config
Jérôme Benoit [Thu, 30 Apr 2026 18:02:48 +0000 (20:02 +0200)] 
chore: remove stale opencode agent config

8 days agofix(deps): update all non-major dependencies (#1820)
renovate[bot] [Thu, 30 Apr 2026 14:56:29 +0000 (16:56 +0200)] 
fix(deps): update all non-major dependencies (#1820)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
8 days agofix(ui-web): align StartTransaction dialog with Authorize dialog
Jérôme Benoit [Thu, 30 Apr 2026 14:51:52 +0000 (16:51 +0200)] 
fix(ui-web): align StartTransaction dialog with Authorize dialog

Add autocomplete="off", use same label/placeholder/hint pattern as
AuthorizeDialog for the RFID field. Remove misleading "optional"
placeholder.

8 days ago[autofix.ci] apply automated fixes
autofix-ci[bot] [Thu, 30 Apr 2026 14:11:32 +0000 (14:11 +0000)] 
[autofix.ci] apply automated fixes

8 days agochore: update serena project config
Jérôme Benoit [Thu, 30 Apr 2026 14:08:13 +0000 (16:08 +0200)] 
chore: update serena project config

8 days agorefactor(ui-web): derive color-scheme from CSS instead of duplicated map
Jérôme Benoit [Thu, 30 Apr 2026 13:59:03 +0000 (15:59 +0200)] 
refactor(ui-web): derive color-scheme from CSS instead of duplicated map

Read the color-scheme property from the resolved theme CSS rather than
maintaining a manual THEME_COLOR_SCHEME record. New themes only need to
declare color-scheme in their CSS file without updating TypeScript code.

8 days agorefactor(ui-web): replace string literals with OCPP16ChargePointStatus enum in tests
Jérôme Benoit [Thu, 30 Apr 2026 13:50:05 +0000 (15:50 +0200)] 
refactor(ui-web): replace string literals with OCPP16ChargePointStatus enum in tests

Use enum constants instead of raw strings for connector status values
in test assertions and mock data for type safety and refactoring ease.

8 days agorefactor(ui-common): use ProcedureName enum instead of string literals in payload...
Jérôme Benoit [Thu, 30 Apr 2026 13:43:01 +0000 (15:43 +0200)] 
refactor(ui-common): use ProcedureName enum instead of string literals in payload builders

Replace procedureName string literals with ProcedureName enum values
for type safety and consistency across builders, consumers and tests.

8 days agofix(ui-web): show # prefix on transaction ID only for numeric IDs
Jérôme Benoit [Thu, 30 Apr 2026 13:38:04 +0000 (15:38 +0200)] 
fix(ui-web): show # prefix on transaction ID only for numeric IDs

OCPP 2.0.x uses UUID strings as transaction IDs where the # prefix is
inappropriate. Only prepend # for OCPP 1.6 numeric transaction IDs.

8 days agofix(ui): make Authorize version-aware for OCPP 2.0.1 stations
Jérôme Benoit [Thu, 30 Apr 2026 13:30:13 +0000 (15:30 +0200)] 
fix(ui): make Authorize version-aware for OCPP 2.0.1 stations

Extract shared OCPP version-aware payload builders into
ui-common/src/utils/payloadBuilders.ts (buildAuthorizePayload,
buildStartTransactionPayload, buildStopTransactionPayload, isOCPP20x,
buildIdToken) and consume them from both Web UI and CLI.

The Web UI Authorize was always sending flat { idTag } regardless of
OCPP version, causing "Request PDU is invalid" on OCPP 2.0.1 stations
which require { idToken: { idToken, type } }.

Closes #1817

8 days agodocs(ui-web): align README with modern skin default and token contract
Jérôme Benoit [Thu, 30 Apr 2026 10:01:02 +0000 (12:01 +0200)] 
docs(ui-web): align README with modern skin default and token contract

8 days agodocs(ui-web): update screenshot to modern skin default
Jérôme Benoit [Thu, 30 Apr 2026 09:54:48 +0000 (11:54 +0200)] 
docs(ui-web): update screenshot to modern skin default

8 days agofeat(ui-web): set modern as default skin
Jérôme Benoit [Thu, 30 Apr 2026 09:33:33 +0000 (11:33 +0200)] 
feat(ui-web): set modern as default skin

8 days agodocs(ui-web): fix 6 stale comments after refactoring
Jérôme Benoit [Thu, 30 Apr 2026 09:23:57 +0000 (11:23 +0200)] 
docs(ui-web): fix 6 stale comments after refactoring

8 days agofix(ui-web): future-proof light-mode pill overrides and fix import ordering
Jérôme Benoit [Thu, 30 Apr 2026 09:15:56 +0000 (11:15 +0200)] 
fix(ui-web): future-proof light-mode pill overrides and fix import ordering

8 days agorefactor(ui-web): fix audit findings — dead code, layer violations, composable extraction
Jérôme Benoit [Thu, 30 Apr 2026 08:46:15 +0000 (10:46 +0200)] 
refactor(ui-web): fix audit findings — dead code, layer violations, composable extraction

8 days agofix(ui-web): add missing .js extensions to relative test imports
Jérôme Benoit [Thu, 30 Apr 2026 08:16:56 +0000 (10:16 +0200)] 
fix(ui-web): add missing .js extensions to relative test imports

8 days agofix(ui-web): harmonize imports, fix promise chain ordering, and remove dead code
Jérôme Benoit [Thu, 30 Apr 2026 08:04:54 +0000 (10:04 +0200)] 
fix(ui-web): harmonize imports, fix promise chain ordering, and remove dead code

8 days agorefactor(ui-web): reorganize composables into core/ infrastructure and shared/composa...
Jérôme Benoit [Thu, 30 Apr 2026 07:35:59 +0000 (09:35 +0200)] 
refactor(ui-web): reorganize composables into core/ infrastructure and shared/composables/

9 days agofeat(ui-web): add dracula, gruvbox-dark, rose-pine themes and fix surface hierarchy
Jérôme Benoit [Thu, 30 Apr 2026 00:24:52 +0000 (02:24 +0200)] 
feat(ui-web): add dracula, gruvbox-dark, rose-pine themes and fix surface hierarchy

9 days agofeat(ui-web): add teal-dark and teal-light themes and fix sap-horizon state colors
Jérôme Benoit [Wed, 29 Apr 2026 23:44:43 +0000 (01:44 +0200)] 
feat(ui-web): add teal-dark and teal-light themes and fix sap-horizon state colors

9 days agorefactor(ui-web): temporarily disable refresh button in modern skin
Jérôme Benoit [Wed, 29 Apr 2026 23:23:50 +0000 (01:23 +0200)] 
refactor(ui-web): temporarily disable refresh button in modern skin

9 days agofix(ui-web): resolve WS race condition causing DISCONNECTED on modern skin
Jérôme Benoit [Wed, 29 Apr 2026 23:09:37 +0000 (01:09 +0200)] 
fix(ui-web): resolve WS race condition causing DISCONNECTED on modern skin

Problem: when skin layouts are lazy-loaded via defineAsyncComponent, the
WebSocket 'open' event can fire before the layout mounts and registers its
listener. The layout then shows DISCONNECTED until manual refresh because
getData() is never triggered.

Root cause: UIClient connects in main.ts constructor (fire-and-forget),
but the skin layout mounts asynchronously after the chunk loads. If the
WS handshake completes during chunk loading, the 'open' event is dispatched
to an EventTarget with zero listeners.

Fix: expose connection state and fetch eagerly when already connected.

- ui-common/WebSocketClient: add `get connected(): boolean` getter
  (returns readyState === OPEN)
- ui-web/UIClient: add `isConnected(): boolean` (delegates to client.connected)
- ui-web/useLayoutData: in onMounted, call getData() immediately if
  isConnected() is true. Otherwise the normal 'open' handler will
  trigger getData() when the connection establishes — no premature
  fetch, no error toast, no duplicate calls.
- ui-web/tests: add isConnected to MockUIClient (defaults to false);
  add 2 explicit tests for both branches of the mount-time check

9 days agofeat(ui-web): implement runtime skin system with classic and modern skins (#1815)
Jérôme Benoit [Wed, 29 Apr 2026 22:25:44 +0000 (00:25 +0200)] 
feat(ui-web): implement runtime skin system with classic and modern skins (#1815)

* feat(ui-web): add opt-in v2 UI with Material-inspired design

Parallel Vue 3 UI at /v2 that mirrors the existing feature set with a
flat Material-inspired palette, card-based station layout, modal
dialogs (Teleport + focus trap), grouped action buttons with clear
hierarchy, and a theme toggle (auto/light/dark, persisted per-user).

The v2 tree is fully self-contained under src/v2/; only three files
outside that folder change:
 - router/index.ts: five /v2/* routes mirroring the existing ones
 - App.vue: v1/v2 toggle link + top-level named <router-view> for v2
   dialogs
 - README.md: note on /v2 opt-in and merge path

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(ui-web): add unit tests for the v2 UI subtree

Covers every v2 file (ActionButton, StatePill, Modal, ConfirmDialog,
SimulatorBar, StationCard, ConnectorRow, V2NotFoundView, the four
dialogs, v2Errors, and V2ChargingStationsView) under a dedicated
tests/unit/v2/ folder so it can be dropped as a unit when v1 is
removed.

Takes the global coverage back above the existing 91/89/83/91
thresholds (lines 93.2%, branches 92.2%, functions 86.1%, statements
92.3%) after the v2 subtree added 643 previously-untested lines.
Mocks the Teleport-based Modal with an inline stub in dialog tests so
wrapper.find() reaches the form inputs.

* feat(ui-web): add skin+theme schema, token contract, and skin registry

- feat(ui-common): add skin and theme fields to configuration schema
  - ConfigurationData: add skin?: string alongside existing theme?: string
  - configurationSchema: add skin and theme as optional string fields
  - Fixes pre-existing gap where theme was typed but not Zod-validated
- feat(ui-web): extend theme CSS files with unified token contract
  - All 3 theme files: add [data-theme=<name>] selector for runtime switching
  - Add 9 new semantic tokens: bg-raised, bg-sunken, state-ok/warn/err/idle,
    spacing-xl, font-size-base/xs; color-scheme: dark|light
  - All existing token names and values unchanged (backward compatible)
- feat(ui-web): add TOKEN_CONTRACT TypeScript interface in shared/tokens
  - 33-entry as const map of semantic name to CSS custom property name
  - Exports TokenName and CssCustomProperty derived types
- feat(ui-web): add skin registry with classic and modern skin definitions
  - SkinDefinition interface with lazy CSS loader for code splitting
  - DEFAULT_SKIN = 'classic'
  - CSS module shim added to shims-vue.d.ts for TypeScript resolution

* feat(ui-web): add useSkin and useTheme composables

- useSkin: module-level singleton for runtime skin switching
  - activeSkinId ref initialized from localStorage (fallback: 'classic')
  - switchSkin() validates, lazy-loads CSS, persists preference
  - Eager load of initial skin styles at module init
- useTheme: module-level singleton for runtime theme switching
  - activeTheme ref initialized from localStorage (fallback: 'tokyo-night-storm')
  - setTheme() sets data-theme attribute + color-scheme on document root
  - ThemeName union type exported for component type safety
  - Themes: catppuccin-latte | sap-horizon | tokyo-night-storm (alphabetical)
- eslint.config.js: add 'catppuccin' to cspell words list

* feat(ui-web): add shared composables and classic skin structure

- feat(ui-web): add shared headless composables
  - useStationStatus: pure OCPP status → 'ok'|'warn'|'err'|'idle' mapping
  - useAddStationsForm: all 12 form fields + submit/reset for adding stations
  - useSetUrlForm: supervision URL form state + validated submit
  - useStartTxForm: start transaction with optional authorize flow
- feat(ui-web): create skins/classic/ directory structure
  - ClassicLayout.vue: table-based root layout (adapted from ChargingStationsView)
  - classic.css: structural tokens for classic skin
  - Full component tree: Container, Button, StateButton, ToggleButton,
    CSTable, CSData, CSConnector, and 3 action components
  - Internal imports use relative paths; @/composables remain shared
- fix(eslint): add skins/classic/ paths to multi-word component name exceptions

* feat(ui-web): create skins/modern/ structure with migrated CSS tokens

- feat(ui-web): modern skin directory structure
  - ModernLayout.vue: card-based root layout (from V2ChargingStationsView.vue)
    - CSS import updated to './modern.css'
    - Component imports updated to './components/...'
    - Theme cycle logic removed (replaced by unified useTheme() in T18)
  - 7 base components + 4 dialog components copied from v2
  - composables/constants.ts: skin-local route/key constants
  - composables/errors.ts: error handling utilities
- feat(ui-web): migrate modern.css CSS tokens to unified contract
  - 0 --v2-primary refs remain (replaced by --color-primary)
  - 0 --v2-state-* refs remain (replaced by --color-state-*)
  - 0 [data-v2-theme] blocks remain (removed — theme via unified system)
  - 242 var(--v2-*) references migrated to --skin-* or --color-* tokens
  - Structural tokens renamed: --v2-space/radius/elev/font/* → --skin-space/radius/shadow/font/*
  - File reduced from 1311 to 1218 lines (93 lines of redundant light overrides removed)

* feat(ui-web): integrate skin system into App.vue, main.ts, and router

- feat(ui-web/App.vue): replace dual-view shell with skin switching shell
  - defineAsyncComponent layoutMap for ClassicLayout + ModernLayout
  - :key={activeSkinId} forces full remount on skin switch
  - Fade transition (opacity 0.2s) between skins
  - App.vue reduced to 46 lines (pure orchestration, no skin logic)
- feat(ui-web/main.ts): replace dynamic theme loading with static imports
  - All 3 theme CSS files loaded eagerly at boot
  - Theme applied via useTheme().setTheme() composable
  - Skin preference initialized from config.skin → useSkin().switchSkin()
  - Config.skin and config.theme both respected with localStorage override
- feat(ui-web/router): remove v2 routes, keep v1 skin-agnostic routes
  - Remove all /v2/* routes and V2 component imports
  - Keep named 'action' view routes for classic skin sidebar panels
  - Action panel components loaded lazily via async imports
  - No skin-specific paths in router (both skins share same URLs)
- fix(ui-web/ClassicLayout.vue): add router-view name='action' for sidebar

* refactor(ui-web): integrate shared composables into both skins and add skin/theme switchers

- refactor(ui-web/classic): action components use shared headless composables
  - AddChargingStations: uses useAddStationsForm() for all form state + submit
  - SetSupervisionUrl: uses useSetUrlForm() for form state + validated submit
  - StartTransaction: uses useStartTxForm() for form state + async submit
- feat(ui-web/classic): add skin/theme selectors to ClassicLayout top bar
  - useSkin() provides activeSkinId, skins, switchSkin
  - useTheme() provides activeTheme, availableThemes, setTheme
  - Two <select> elements for runtime skin and theme switching
- feat(ui-web/modern): add skin selector to SimulatorBar
  - useSkin() integrated for runtime skin switching

* docs(ui-web): document skin system and update configuration

- Add skin field to config-template.json (skin: 'classic' default)
- Add Skins section to README after Theming section
- Add skin row to configuration reference table
- Remove 'Trying the v2 UI' section (v2 is now the 'modern' skin)

* test(ui-web): add skin system tests and fix lint warnings

- test(ui-web): add tests for skin system composables
  - useSkin: 7 tests (init, switchSkin, persistence, invalid skin)
  - useTheme: 9 tests (init, setTheme, DOM attribute, localStorage, color-scheme)
  - useStationStatus: 11 tests (all OCPP connector statuses + WS variants)
  - registry: 7 tests (DEFAULT_SKIN, skin definitions, required fields)
- fix(eslint): add focusables, cret to cspell words list
- fix(eslint): disable vue/order-in-components for test files
- fix(ui-web): fix perfectionist/sort-imports in ToggleButton, CSConnector
- fix(ui-web): add JSDoc param descriptions in SimpleComponents.test.ts
- fix(ui-web): auto-fix html-indent and comma-dangle formatting

* test(ui-web): add form composable tests and fix coverage thresholds

- test(ui-web): add tests for shared form composables
  - useAddStationsForm: 7 tests (init, templates, reset, submit)
  - useSetUrlForm: 6 tests (init, validation, submit)
  - useStartTxForm: 7 tests (init, submit, authorize flow)
- fix(ui-web/vitest): exclude skin component copies from coverage
  - skins/classic/components/** (copies of src/components/)
  - skins/classic/ClassicLayout.vue (adaptation of ChargingStationsView)
  - skins/modern/components/** (copies of src/v2/components/)
  - skins/modern/ModernLayout.vue (adaptation of V2ChargingStationsView)
  - skins/modern/composables/** (skin-local constants)
  - shared/tokens/** (pure as const, no logic)
  - src/v2/** (original v2 directory)
- fix(ui-web): update v2 test imports to skins/modern paths
- Coverage thresholds met: 91.35% stmts, 91.64% branches, 85.18% funcs, 91.44% lines

* refactor(ui-web/modern): use shared composables in modern skin dialogs

- AddStationsDialog: uses useAddStationsForm() for all form state + submit
- SetSupervisionUrlDialog: uses useSetUrlForm() for form state + validated submit
- StartTransactionDialog: uses useStartTxForm() for async authorize + start flow
- ConnectorRow: uses getConnectorStatusVariant() for status → variant mapping

Eliminates duplicated business logic between classic and modern skins.
Both skins now share the same headless composables for all form operations.

* refactor(ui-web): move skin registry to shared layer (fixes P2 layer inversion)

Move skins/registry.ts → shared/skins/registry.ts so the shared layer no longer
depends on the skins layer. Update all import paths (useSkin.ts, 2 test files).
CSS dynamic imports now use absolute @/ paths for clarity.

* refactor(ui-web): apply audit remediation Waves 2-4

Wave 2 (A2+A3): Modern skin cleanup
- Remove redundant per-skin theme cycle system (themeMode, effectiveTheme,
  cycleTheme, prefersDark, data-v2-theme watchEffect) from ModernLayout.vue
- SimulatorBar: replace theme cycling button with useTheme() select dropdown
- Replace router-based dialogs with state-based dialog management
  - ModernLayout: showAddDialog/showSetUrlDialog/showStartTxDialog/showAuthorizeDialog refs
  - StationCard/ConnectorRow: emit events instead of router.push
  - All 4 dialogs: emit('close') instead of router.push(V2_CHARGING_STATIONS)
- Remove V2_ROUTE_NAMES and V2_THEME_KEY from constants (only V2_UI_SERVER_INDEX_KEY remains)

Wave 3 (A4+A5+A6): Composable polish
- useAddStationsForm: accept optional onFinally callback, remove resetToggleButtonState
  (UI concern moved to skin layer — classic passes reset+navigate, modern passes emit close)
- useTheme: add typeof document guard for SSR safety
- useSkin: add switching flag to prevent concurrent switchSkin race condition

Wave 4 (A7+A8): CSS cleanup
- Prune 9 dead --skin-* token definitions from modern.css
- Remove 6 hardcoded dark-mode fallback values (#1f2335, #8089b3)
  (themes loaded eagerly — fallbacks unnecessary and leak wrong palette)

* test(ui-web): harden skin system tests with error paths and boundary conditions

Add 10 new tests covering gaps identified by audit:
- useSkin: loadStyles() rejection, concurrency guard, same-skin no-op
- useTheme: invalid theme name guard, SSR document undefined safety
- useAddStationsForm: onFinally callback invocation, boundary (n=0), reactive templates
- useSetUrlForm: empty URL validation, valid URL happy path

Strengthen thin assertions: add secondary assertions to single-assertion tests.
Fix lint: remove non-null assertions (use expect+guard pattern), remove unused vi import,
replace 'as any' with typed function cast for invalid-input test.

Coverage improved: 91.65% stmts, 92.04% branches, 85.18% funcs, 91.72% lines.

* [autofix.ci] apply automated fixes

* fix(ui-web): address PR review comments

- fix(eslint): correct 'cret' typo to 'secret' in cspell words list
- fix(ui-web/useSkin): validate activeSkinId against registry on init
  Add getValidSkinId() helper — falls back to DEFAULT_SKIN if localStorage
  contains an unregistered skin id (prevents inconsistent UI state)
- fix(ui-web/CSData): wrap new URL() in try/catch for malformed supervision URLs
  Returns raw string on parse failure instead of crashing the component
- fix(ui-web/router): NOT_FOUND route renders 404 message instead of blank
- fix(ui-web/useStartTxForm): submitForm returns Promise<boolean>
  Dialog only closes on success (true); stays open on error for user retry

* [autofix.ci] apply automated fixes

* refactor(ui-web): complete P4 remediation — DRY, dead code removal, test migration

A11 — Derive skinLayoutMap from registry (DRY):
- Add loadLayout field to SkinDefinition interface
- App.vue derives layout from registry.skins.find() instead of hardcoded map
- Adding a skin now requires changes in ONE file only (registry.ts)

PR#2 — Router cleanup:
- Home route uses { render: () => null } instead of empty template anti-pattern
- 404 route renders meaningful content

PR#11 — Remove dead src/v2/ code + migrate tests:
- Delete entire src/v2/ directory (dead code — app loads from skins/modern/)
- Migrate 7 test files from tests/unit/v2/ → tests/unit/skins/modern/
- Update all imports from @/v2/ → @/skins/modern/
- Adapt tests to modern skin interfaces:
  - ConnectorRow/StationCard: emit events instead of router.push
  - Dialogs: emit 'close' instead of navigating
  - SimulatorBar: remove theme cycling tests (replaced by useTheme select)
  - ChargingStationsView → ModernLayout: remove theme tests, update stubs
- Remove src/v2/ from vitest coverage exclusions

PR#8 — Test strengthening:
- useStationStatus: add second assertion to each test (2.0+ avg)
- Add exhaustiveness test covering all 11 known status values
- Layout exclusions kept (justified: integration-level, ~67% coverage without)

Quality gates: typecheck ✓ | lint 0 problems ✓ | build ✓ | 443 tests ✓
Coverage: 91.54% stmts | 91.79% branches | 84.93% funcs | 91.77% lines

* [autofix.ci] apply automated fixes

* refactor(ui-web): audit round 2 — delete dead code, fix elegance, unify state

P1 — Delete dead code:
- Remove src/components/ and src/views/ (dead — classic skin has own copies)
- Remove 10 Layer 1 test files targeting dead components (152 redundant tests eliminated)
- Update eslint.config.js: remove dead path exceptions

P2 — Fix README:
- Correct registry path: src/skins/registry.ts → src/shared/skins/registry.ts
- Remove reference to non-existent skinLayoutMap

P5 — Code elegance (useAddStationsForm):
- Extract makeInitialState() factory (eliminates DRY violation)
- Extract nonEmpty() helper (eliminates 4 verbose ternaries)
- Both with proper JSDoc as required by project lint rules

P7 — Unify localStorage key:
- Modern skin now uses UI_SERVER_CONFIGURATION_INDEX_KEY (same as classic)
- Delete skins/modern/composables/constants.ts (was only V2_UI_SERVER_INDEX_KEY)
- Server selection persists correctly when switching between skins

Quality: typecheck ✓ | lint 0 ✓ | 291 tests ✓ | coverage 91.34/89.89/88.57/91.97 ✓

* style(ui-web): align tests with TEST_STYLE_GUIDE.md conventions

P3 — Test naming: prefix all 291 test names with 'should' per style guide
  (it('returns ok') → it('should return ok'))

P4a — Single top-level describe: wrap multi-describe files
  - useStationStatus.test.ts: 2 describes → 1 parent
  - Dialogs.test.ts: 4 describes → 1 parent 'Modern skin dialogs'
  - SimpleComponents.test.ts: 4 describes → 1 parent 'Modern skin simple components'

P4b — Move vi.clearAllMocks() from beforeEach to afterEach:
  - useAddStationsForm.test.ts
  - useSetUrlForm.test.ts
  - useStartTxForm.test.ts

All 291 tests still pass. Coverage maintained.

* fix(ui-web): blocking audit fixes — error display, config validation, router guard

B1: Add inline error display to StartTransactionDialog (matches AuthorizeDialog pattern)
B2: Validate config.json against Zod schema at startup with user-visible errors
B3: Add skin-aware router navigation guard redirecting classic routes under modern skin

All 292 tests pass. TypeScript clean.

* refactor(ui-web): Wave 2 should-fix batch — DRY, dialogs, credentials, performance

S1+S3: Make dialog submit async with pending state; restore authorizeIdTag=true default
S2: Fix credential clearing — always send empty strings (not undefined)
S4: Use shallowRef for wholesale-replaced providers (performance)
S5: Extract useLayoutData composable eliminating 50-line cross-skin duplication
S10: Add error/loading fallbacks to defineAsyncComponent
S11: Self-host Onest font (remove Google Fonts external dependency)
S12: Remove useSkin eager fire-and-forget (canonical init via main.ts)

All 292 tests pass.

* refactor(ui-web): final audit batch — dead code, token test, v2→modern rename, naming

S7: Remove dead getWsStatusVariant export
S8: Add token contract enforcement test (all themes must define all tokens)
S14: Remove modern/composables from coverage exclusion
G1: Pre-create defineAsyncComponent map for skin switching
G3: Rename v2- CSS prefix to modern- across all modern skin files (~142 occurrences)
G4: Rename switchSkin→setSkin for naming coherence with setTheme
G7: Rename ChargingStationsView.test.ts→ModernLayout.test.ts

All 292 tests pass.

* refactor(ui-web): import storage keys from composables instead of duplicating literals

* docs(ui-web): document skin routing architecture decisions

* fix(ui-web): disable CSS transitions during theme switch and remove redundant colorScheme JS write

* refactor(ui-web): extract classic-specific toggle reset from shared useStartTxForm

* fix(ui-web): tie refresh spinner to actual data loading state instead of setTimeout

* fix(ui-web): align authorizeIdTag default to false matching original behavior

* test(ui-web): add unit tests for useLayoutData shared composable

* fix(ui-web): exclude bootstrap components from coverage + improve loading branch coverage

* fix(ui-web): resolve lint warnings in new and modified composables

* chore(ui-web): apply formatter output (no logic changes)

* test(ui-web): align coverage config with actual test scope and fix lifecycle placement

* fix(ui-web): add error handling and status feedback to useSkin

* refactor(ui-web): extract useSimulatorControl shared composable

* refactor(ui-web): decouple router from hardcoded skin identifiers

* style(ui-web): replace hardcoded colors with token references

* refactor(ui-web): fix reactivity patterns in modern skin and shared composables

* refactor(ui-web): align classic skin with Vue.js best practices

* fix(ui-web): remove vestigial v2 naming and dead navigation link

* test(ui-web): add unit tests for useSimulatorControl composable

* chore(ui-web): fix lint issues in useSimulatorControl composable and tests

* fix(ui-web): replace innerHTML with textContent for config error display

* style(ui-web): fix test naming grammar to follow style guide

* test(ui-web): add coverage for SkinLoadError and SkinLoading components

* refactor(ui-web): inject layout data into useSimulatorControl; centralize uiServerConfigurations

* refactor(ui-web): extract useAsyncAction composable from modern skin components

* fix(ui-web): mock useConfiguration in useLayoutData tests after T3 refactor

* style(ui-web): apply format fixes and resolve lint errors from quality gate run

* fix(ui-web): address audit findings for skin system robustness and code quality

- Wrap localStorage access in try/catch for Safari Private Browsing and corruption resilience
- Make switchSkin() idempotent for already-active skin (fixes preference reset on reload)
- Add timeout and pending guard to server switch with stale listener cleanup
- Return readonly(pending) from useAsyncAction, wrap callbacks in try/catch
- Remove key-based forced remount in ClassicLayout (performance improvement)
- Add recovery button in SkinLoadError for broken skin JS chunk recovery
- Add cancelled flag in StartTransactionDialog to prevent orphaned operations
- Fix useStartTxForm to catch internally instead of re-throwing (contract fix)
- Rename useStationStatus to stationStatus (pure utility, not a composable)
- Extract shared theme CSS element resets to base.css (DRY)
- Fix test style violations (JSDoc headers, grammatical names, single describe)
- Replace inline fieldset styles with CSS class in AddStationsDialog
- Remove redundant Array.isArray() checks in ClassicLayout
- Remove unused SkinDefinition.description field from registry
- Rename modern composables/ to utils/ (contains pure utilities, not composables)
- Use CSS variable fallbacks in 404 route inline template
- Add classic skin smoke tests (ClassicLayout renders without crashing)
- Add timeout tests for useSimulatorControl server switch

* fix(ui-web): address audit findings across skin system

- fix dialog close-on-failure: submitForm returns boolean, dialogs stay open on error
- fix SetSupervisionUrlDialog reconnect race: await submitForm before reconnect
- fix useAsyncAction promise chain: reorder to .then().catch().finally()
- fix NOT_FOUND route: render in default view for both skins
- fix useSkin eager CSS load race with main.ts initialization
- fix useSimulatorControl timeout leak and listener stacking
- fix stationStatus case-insensitive matching
- fix CSData method calls converted to computed properties
- fix modern.css hardcoded values extracted to skin tokens
- fix SkinLoadError reload loop protection via sessionStorage counter
- fix authorizeIdTag default to true (matching source PR #1804)
- fix numberOfStations input bounded to max=100
- fix useTheme: export AVAILABLE_THEMES, setTheme accepts string
- fix main.ts: remove unsafe ThemeName cast
- fix Utils.ts: add try/catch to deleteFromLocalStorage
- fix AuthorizeDialog: reactive→ref for single field
- fix SkinLoading: add defineOptions for DevTools name
- fix test stale naming (V2ModalStub→ModalStub, JSDoc descriptions)
- fix test weak assertions replaced with strict values
- add comprehensive classic skin component tests (91%+ coverage)
- remove classic skin coverage exclusions

* fix(ui-web): resolve skin system audit findings — FOUC, token validation, test quality

- Coordinate CSS+layout loading in defineAsyncComponent to prevent FOUC
- Add markRaw() to async component definitions for correct reactivity
- Set data-skin attribute on <html> for observability and future CSS scoping
- Add dev-mode TOKEN_CONTRACT runtime validation after skin CSS load
- Remove ~15 redundant test assertions (not.toBe/not.toBeNull after positive equality)
- Fix 4 test naming grammar issues to match 'should [verb]' convention
- Remove duplicate renderTemplates reactivity test (subsumed by more thorough test)
- Add edge case tests: data-skin attribute, invalid localStorage theme fallback

* fix(ui-web): address audit findings for skin system quality

- Replace router 404 inline template with h() render function (fixes
  runtime compiler requirement)
- Add onError callback to useStartTxForm for rich error display in
  StartTransactionDialog
- Clear lastError on successful skin switch in useSkin
- Remove static CSS imports from layout components (single loading path
  via registry)
- Extract shared getConnectorEntries() and getATGStatus() utilities
- Add :pending prop to AddStationsDialog and SetSupervisionUrlDialog
  submit buttons
- Clear skin-error-reload-count on successful skin switch
- Fix misleading useSkin test assertion for singleton behavior

* fix(ui-web): address comprehensive audit findings for skin system

- fix(CSData): wrap URL parse in try/catch to prevent row crash
- refactor(useStartTxForm): consolidate 5 positional params into config object
- fix(vitest): remove router/App.vue from coverage exclusions
- fix(Utils): add try/catch to getLocalStorage and deleteLocalStorageByKeyPattern
- refactor(registry): remove unnecessary async wrappers from loadStyles
- fix(useTheme): export DEFAULT_THEME constant, use in main.ts
- fix(useSimulatorControl): replace eslint-disable with inline no-op comment
- fix(useAsyncAction): add explanatory comment for eslint-disable
- test(contract): strengthen CSS token assertion with regex validation
- test(registry): use exact count assertion, add loadLayout test
- test(useSkin): add switching and lastError ref coverage
- test(useSetUrlForm): fix floating promise lint errors in tests

* refactor(ui-web): address audit findings for skin system quality

- Change useAsyncAction to accept factory function (prevents guard bypass)
- Simplify TOKEN_CONTRACT from object map to typed string array
- Await submitForm result in classic SetSupervisionUrl before navigation
- Clear skin-error-reload-count on successful 'already active' path
- Use registry label in SkinLoadError instead of hardcoded 'Classic'
- Add dev-mode console.debug to localStorage error paths
- Consolidate theme CSS extension sections into main sections
- Move registry.ts to skins/ for better colocation
- Rename activeTheme to activeThemeId for naming consistency
- Add shared/composables barrel index.ts
- Extract modal CSS to Modal.vue scoped styles
- Add module JSDoc to stationStatus.ts and useAsyncAction.ts
- Improve test isolation (afterEach DOM cleanup, wrapper.unmount)
- Add App.vue smoke test, router guard test, localStorage edge case test
- Add useAsyncAction error resilience tests
- Wrap multi-describe test files in parent describe blocks
- Fix useStationStatus test describe name mismatch

* fix(ui-web): address comprehensive audit findings for skin system

- Add @prefers-reduced-motion support in modern skin (WCAG 2.1 AA)
- Migrate useExecuteAction consumers to async/await pattern with useToast
- Mark useExecuteAction as @deprecated in favor of useAsyncAction
- Wrap sessionStorage access in try/catch for privacy mode compatibility
- Use shallowRef for simulatorState in useLayoutData (performance)
- Wrap exposed templates ref in readonly() in useAddStationsForm
- Extract makeInitialState() factory in useSetUrlForm (DRY)
- Extract shared formatSupervisionUrl() utility used by both skins
- Set html lang=en for accessibility
- Use CSS variable fallback in config error display
- Update token contract JSDoc to clarify mandatory vs overridable tokens
- Document shadow token browser support decision
- Add FOUC prevention comment in useSkin module-level effect
- Add defineOptions name to classic Button component
- Fix toast mock shadowing in test files
- Fix describe block naming in stationStatus and registry tests

* refactor(ui-web): address audit findings — DRY tokens, CSP compat, remove deprecated code

- Move shared spacing/typography tokens from theme files to base.css (DRY)
- Replace inline style.cssText with CSS class for CSP compatibility
- Replace dynamic <style> injection with class toggle in useTheme
- Migrate useSimulatorControl from deprecated useExecuteAction to direct toast pattern
- Remove redundant pending guard in AddStationsDialog
- Update token contract test to check combined theme+base CSS

* refactor(ui-web): address code quality audit findings

- Refactor useAsyncAction to async/await with void IIFE pattern
- Fix validation/pending guard order in useSetUrlForm
- Add pending ref to useStartTxForm for API consistency
- Rename shadowed pending in SetSupervisionUrlDialog
- Remove redundant refreshData wrapper in ModernLayout
- Extract PassthroughRoute constant in router
- Add visible error state on config fetch failure
- Document token contract split and shared→skins coupling
- Fix test isolation in useAddStationsForm and useTheme tests
- Fix misleading test name in useSkin test

* refactor(ui-web): remove deprecated useExecuteAction dead code

* fix(ui-web): address audit findings from PR review

* fix(ui-web): correct outdated localStorage error comment

The comment incorrectly attributed the try/catch to Safari Private
Browsing (fixed in Safari 11, September 2017 — WebKit bug #157010).
Updated to document the actual modern throw scenarios: genuine quota
exhaustion and SecurityError from browser cookie-blocking policies.

* refactor(ui-web): resolve all audit findings from comprehensive PR review

Address 63 findings across 7 categories identified by multi-agent
cross-validated code audit:

Architecture & Correctness:
- Replace useSkin switching boolean with Promise coalescing pattern
- Add static fallback in App.vue for cascade skin load failure
- Add toast notification on skinOnly route redirect
- Fix DEFAULT_THEME fragile array index

Code Quality:
- Convert useSimulatorControl to async/await (repo convention)
- Fix ActionButton click re-emit anti-pattern (Vue  fallthrough)
- Fix StartTransactionDialog unmount guard (reactive ref + finally)
- Fix SetSupervisionUrlDialog reactive pre-fill (watch immediate)
- Simplify ModernLayout state (flat ref vs object wrapper)
- Extract stripStationId to shared utils
- Await submitForm in classic AddChargingStations

CSS & Accessibility:
- Fix non-standard font-weight: 650 → 600
- Remove dead CSS classes (template-label, title-sub, dead tokens)
- Fix StationCard role=button accessibility anti-pattern
- Add missing form labels in AddStationsDialog
- Deduplicate SVG in ConnectorRow (conditional path)
- Replace inline styles with CSS classes
- Replace hardcoded class string with data attribute in Modal

Classic Skin Polish:
- Bind pending prop to disable buttons during async actions
- Remove redundant v-show guard on template options
- Replace non-null assertion with guard pattern in CSConnector
- Normalize import paths for consistency
- Use safe localStorage helpers in ToggleButton

Test Suite:
- Fix BLOCKER: replace hardcoded localStorage key with constant
- Fix timer leak: move vi.useFakeTimers to beforeEach/afterEach
- Add missing assertions (useAsyncAction pending reset)
- Remove duplicate tests (useSetUrlForm, useStationStatus)
- Add error-path tests (useLayoutData, useSimulatorControl)
- Normalize @file headers and describe block names
- Fix .js extension on setup/helpers imports

* refactor(ui-web): address comprehensive audit findings

Fix all findings from 6-dimension blind cross-validated review:

MAJOR:
- Fix body scroll lock on skin switch mid-dialog (watch activeSkinId)
- Add useSetUrlForm rejection/error path test coverage

MINOR:
- CSTable v-show → v-if for initial render performance
- defineAsyncComponent for modern dialog lazy loading
- Remove redundant submitting ref in SetSupervisionUrlDialog
- Add prefers-reduced-motion media queries for accessibility
- Type meta.skinOnly as SKIN_IDS union for type safety
- Rename modern-confirm__* to modern-dialog__* for CSS consistency
- Add classic- prefix to classic skin scoped CSS classes
- Clean up WS listeners in useSimulatorControl on scope dispose
- Consolidate switchPromise cleanup to single location
- Document CSP requirements in README

NIT:
- Add defineOptions to Container.vue (multi-word component name)
- Standardize emit naming (remove $ prefix on defineEmits)
- Extract template inline type casts to helper functions
- Restore per-step error label in StartTransactionDialog
- Rename stationStatus describe to match module name
- Replace toBeTruthy() with toHaveLength(1) for emission checks
- Remove dead flushAllPromises alias from helpers

Quality gates: format ✓ | typecheck ✓ | lint ✓ | build ✓
Coverage: Statements 91.98% | Branches 88.59% | Functions 88.03% | Lines 92.55%
Tests: 449/449 pass

* refactor(ui-web): resolve 46 audit findings from MACAR cross-validated review

Address ALL findings from 6-dimension blind cross-validated audit (D1-D6 + D7):

Architecture & Design (D1):
- Fix promise coalescing race: concurrent switchSkin waits then retries
- Scope modern.css :root tokens under html[data-skin=modern]
- Add error boundaries (timeout/loading/error) to modern async dialogs
- Add dev-mode token validation on theme switch
- Add readonly(switching) to useSkin return

Vue.js State-of-Art (D2):
- Remove isMounted anti-pattern in StartTransactionDialog
- Add explicit useRouter() in CSConnector.vue
- Add type guard for route query OCPPVersion
- Add .js extensions to all bare TypeScript imports
- Rename currentSkinLayout → activeSkinLayout

Porting Fidelity (D3):
- Restore CSMS URL row keyboard accessibility (role/tabindex/keyboard)
- Revert connector status colors: charging/occupied → warn (amber)
- Add template-empty validation guard in useAddStationsForm

Code Factorization (D4):
- Extract useConnectorActions composable (eliminates 5x duplication)
- Extract useStationActions composable (eliminates 5x duplication)
- Remove redundant pending ref in StartTransactionDialog

Test Quality (D5):
- Fix describe block names to match module names (4 files)
- Fix grammar in test names (2 occurrences)
- Convert for-of to it.each in contract test
- Add 17 new tests for uncovered branches

Naming & Terminology (D6):
- Rename setTheme → switchTheme (verb consistency with switchSkin)
- Move stationStatus.ts from composables/ to shared/utils/
- Add classic- prefix to all un-prefixed classic skin CSS classes
- Rename stationStatus test file to match source
- Add dev-mode warning to useUIClient singleton fallback
- Create shared StationIdentifier interface

Quality gates: format ✓ | typecheck ✓ | lint ✓ | build ✓ | test:coverage ✓
Coverage: Statements 92.16% | Branches 88.05% | Functions 87.7% | Lines 92.64%
Tests: 466/466 pass (17 new tests added)

* fix(ui-web): remove isMounted anti-pattern and adjust branch threshold

Remove unused isMounted ref/onBeforeUnmount in StartTransactionDialog
(Vue 3 anti-pattern — refs are safe to assign after unmount).
Lower branch threshold 88→87% to accommodate dead-code removal.

Quality gates: format ✓ | typecheck ✓ | lint ✓ | build ✓ | test:coverage ✓
Coverage: Statements 92.22% | Branches 88.05% | Functions 87.91% | Lines 92.71%
Tests: 466/466 pass

* refactor(ui-web): resolve comprehensive audit findings from MACAR 5-perspective review

Address all findings from Multi-Agent Cross-validated Audit Review:

Major fixes:
- Add useConnectorActions.test.ts (28 tests) covering all action paths
- Add useStationActions.test.ts (23 tests) covering all action paths
- Fix 3 assertion-less tests in ClassicLayout and ModernLayout

Component renames (Vue Style Guide A1/B2 compliance):
- Container.vue → ClassicContainer.vue
- Button.vue → ClassicButton.vue
- Modal.vue → ModernModal.vue

Architecture improvements:
- Add explicit return types to useConnectorActions and useStationActions
- Add .js extension to 4 barrel imports (ESM compliance)
- Redirect 404 route to / (dead code elimination)
- Remove dual stationStatus export from composables barrel
- Extract repeated emit expressions in StationCard to named methods

Naming/terminology:
- Rename switching → isSwitching (boolean predicate convention)
- Fix modern.css comment conflating palette with skin

Robustness:
- Replace typeof sessionStorage checks with try/catch
- Document dual style-loading paths (App.vue + useSkin)
- Document useLayoutData register/unregister API surface
- Add test cleanup (vi.restoreAllMocks) to classic tests

Coverage: 90.74% → 92.45% statements, 86.44% → 88.54% functions

* refactor(ui-web): address audit findings for skin system quality

- Add SkinName type and narrow SkinDefinition.id for compile-time safety
- Extract shared validateTokenContract() utility with consistent rAF timing
- Remove dual CSS loading from defineAsyncComponent (useSkin is sole authority)
- Refactor useSimulatorControl to delegate start/stop to useAsyncAction
- Extract defineAsyncDialog helper to reduce repetition in ModernLayout
- Harmonize useSkin/useTheme return naming (availableSkins/availableThemes)
- Move skinOnly guard to per-route beforeEnter for better code locality
- Fix AVAILABLE_THEMES type widening (preserve narrow union type)
- Add missing onError callback tests for useStartTxForm composable
- Fix test describe block naming consistency

* refactor(ui-web): address 7 cross-validated audit findings for skin system

- M1: add scope-disposal guard to useAsyncAction preventing
  post-unmount callback execution and state mutations
- M2: add bounds validation on server index in handleUIServerChange
  with safe rollback on out-of-range values
- m1: move body overflow reset from App.vue into useSkin composable
  (collocate DOM manipulation with skin switch logic)
- m2: refactor useAsyncAction.run() from 5 positional parameters to
  options object pattern for improved readability and safety
- m3: fix inaccurate JSDoc in stationStatus.ts referencing wrong path
- m4: add console.warn when skin layout map fallback activates
- m5: add safety comment for useToast() usage in router guard

New tests: disposal guard (2), bounds validation (2)

* style(ui-web): condense verbose inline comments and remove self-evident ones

- Compress 9 multi-line comments to single-line equivalents
- Remove 3 unnecessary comments (self-evident from context)
- No behavioral changes

* fix(ui-web): theme switching CSS specificity — non-default themes now override

All theme files used `:root, [data-theme='name']` with equal specificity (0,1,0).
Since all CSS is loaded eagerly, the last-imported theme (tokyo-night-storm)
always won via cascade order, making theme switching visually ineffective.

Fix: non-default themes use `:root[data-theme='name']` (specificity 0,2,0)
which beats the default `:root` selector regardless of import order.
Default theme retains `:root` as pre-JS fallback.

* fix(ui-web): resolve CI EnvironmentTeardownError on Node 22.x

Vitest teardown race condition: defineAsyncComponent dynamic imports
resolve after jsdom environment destruction on slower Node runtimes.

Fix: use pool 'forks' for full process isolation per test file,
preventing cross-file module resolution races during teardown.

Also: use symmetric :root[data-theme] selectors for all themes with
data-theme attribute set in index.html for zero-flash pre-JS rendering.

* fix(ui-web): address audit findings — Vue best practices, test dedup, DRY

- refactor(useConnectorActions): adopt MaybeRefOrGetter + toValue()
- feat(useTheme): add lastError ref for invalid theme feedback
- refactor(localStorage): namespace key to ecs-ui-server-index with migration
- refactor(ClassicLayout): replace simulatorLabel fn with computed properties
- refactor(tests): remove ~40 redundant composable-logic assertions from component tests
- refactor(tests): replace toBeTruthy/toBeFalsy with strict alternatives
- test(CSTable): add basic rendering and event delegation test
- test(useSimulatorControl): add AAA comments to complex tests
- refactor(shared/utils): extract getSelectValue to shared/utils/dom.ts
- docs: add token contract, singleton state, and skin CSS scoping docs

* docs(ui-web): remove browser requirement and CSP notes from README

---------

Co-authored-by: Daniel <7558512+DerGenaue@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
9 days agochore(deps): update sonarsource/sonarqube-scan-action action to v7.2 (#1816)
renovate[bot] [Wed, 29 Apr 2026 09:18:35 +0000 (11:18 +0200)] 
chore(deps): update sonarsource/sonarqube-scan-action action to v7.2 (#1816)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
10 days agochore: release main (#1808) cli@v4.5.1 ocpp-server@v4.5.1 simulator@v4.5.1 ui-common@v4.5.1 v4.5
Jérôme Benoit [Tue, 28 Apr 2026 17:06:10 +0000 (19:06 +0200)] 
chore: release main (#1808)

* chore: release main

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
10 days agofix(simulator): quote command names in log messages for readability
Jérôme Benoit [Tue, 28 Apr 2026 15:37:12 +0000 (17:37 +0200)] 
fix(simulator): quote command names in log messages for readability

10 days agofix(simulator): set postTransactionDelay to 2s on ABB station templates
Jérôme Benoit [Tue, 28 Apr 2026 13:22:07 +0000 (15:22 +0200)] 
fix(simulator): set postTransactionDelay to 2s on ABB station templates

10 days agofix(deps): update all non-major dependencies (#1814)
renovate[bot] [Tue, 28 Apr 2026 12:10:41 +0000 (14:10 +0200)] 
fix(deps): update all non-major dependencies (#1814)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
10 days agofix(simulator): add connector Finishing state lifecycle simulation (#1227) (#1812)
Jérôme Benoit [Tue, 28 Apr 2026 12:06:34 +0000 (14:06 +0200)] 
fix(simulator): add connector Finishing state lifecycle simulation (#1227) (#1812)

* fix(simulator): add finishingStatusDelay tunable to station template

* fix(simulator): delay Available status after transaction end for OCPP 2.0.x

* fix(simulator): guard RemoteStartTransaction during Finishing state

* test(simulator): add finishing delay tests for OCPP 1.6 and 2.0.x

* docs(simulator): document finishingStatusDelay template tunable

* [autofix.ci] apply automated fixes

* refactor(simulator): audit corrections — rename, DRY, double-send, guard, safety

* [autofix.ci] apply automated fixes

* fix(simulator): fix JSDoc placement and document early return safety

* fix(simulator): address review comments — meter values, tests, docs

* fix(simulator): reset connector status before sending Available after delay

* fix(simulator): restore original operation ordering in delay=0 path

* fix(simulator): add debug log for RemoteStart rejection during Finishing state

* fix(simulator): harmonize log messages with existing codebase patterns

* refactor(simulator): extract DRY helpers and fix comment in StopTransaction handler

* fix(simulator): simplify Finishing guard to check status alone in RemoteStartTransaction

* fix(simulator): remove misleading log heuristic in OCPP 2.0 StartTransaction guard

* test(simulator): restructure postTransactionDelay tests per ModuleName-Feature naming convention

* fix(simulator): resolve Mock<Function> type mismatch in OCPP 2.0 postTransactionDelay test

* fix(simulator): prevent double power-divider decrement during shutdown with postTransactionDelay

* fix(simulator): prevent double-stop during shutdown using spec-compliant guards

* fix(simulator): restore resetConnectorStatus after sleep to prevent ATG race condition

* fix(simulator): reconcile stale connector state on boot when transactionId is missing

* [autofix.ci] apply automated fixes

* fix(simulator): also reconcile Finishing state connectors on boot for OCPP 1.6

* fix(simulator): delete transactionId before delay in OCPP 1.6 to prevent duplicate RemoteStop

* fix(simulator): use OCPP16ChargePointStatus in version-specific test code

* fix(simulator): harmonize log messages with existing codebase format

* fix(simulator): make OCPP 1.6 connector unlock unconditional per spec §4.10

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
11 days agochore(simulator): harmonize log messages across codebase (#1813)
Jérôme Benoit [Mon, 27 Apr 2026 21:52:08 +0000 (23:52 +0200)] 
chore(simulator): harmonize log messages across codebase (#1813)

* chore(simulator): add moduleName prefix to ChargingStation log messages

* fix(simulator): downgrade unknown incoming OCPP command log to warn level

* chore(simulator): harmonize remaining log messages across codebase

* chore(simulator): harmonize state and lifecycle log message wording

* fix(simulator): use event handler names instead of constructor in log prefixes

* fix(simulator): correct grammar errors in log messages

* fix(simulator): correct 'non existing' to 'non-existent' in log messages