diff --git a/docs/superpowers/plans/2026-08-05-javaTest-dispatch-visualizer.md b/docs/superpowers/plans/2026-08-05-javaTest-dispatch-visualizer.md new file mode 100644 index 0000000..1fd2623 --- /dev/null +++ b/docs/superpowers/plans/2026-08-05-javaTest-dispatch-visualizer.md @@ -0,0 +1,450 @@ +# javaTest Dispatch Visualizer Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build a localhost-only HTML audit console that runs the existing Java scheduler for arbitrary A-to-B tasks and the complete 240-task historical workload, then renders selected vehicles, planned paths, tick movement, failures, and safety evidence. + +**Architecture:** Add a focused point-to-point input factory beside the simulation domain, a visualizer service that invokes the existing simulator, a deterministic map-to-JSON mapper, and a Java 8 `HttpServer` that serves same-origin static assets and APIs. The browser never replans routes; it draws `plannedPath`, map coordinates, and `MOVE` events returned by Java. + +**Tech Stack:** Java 8, Maven, Jackson 2.17.2, JUnit 5.10.2, JDK `HttpServer`, HTML5, CSS, vanilla JavaScript, SVG, Playwright for browser QA. + +## Global Constraints + +- Work only under `javaTest` plus the approved design/plan documents. +- Reuse `HistoricalDispatchSimulator`, `GridPathPlanner`, `VehicleAssignmentService`, traffic arbitration, PLC simulation, archive, and WMS idempotency logic. +- Do not add Spring, Node build dependencies, database connections, PLC connections, or WMS connections. +- Bind the visualizer to `127.0.0.1`; default port is `18084`. +- Do not accept arbitrary filesystem paths or execute user-supplied commands. +- Historical `agv_id` and `path_info` are comparison inputs only. +- The UI must distinguish approach path, service path, and traversed path. +- The UI must remain an offline audit tool and must not claim to guarantee production safety. +- Preserve unrelated worktree changes. + +--- + +### Task 1: Point-to-Point Simulation Input and Fault Presets + +**Files:** +- Create: `javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/PointToPointSimulationInputFactory.java` +- Create: `javaTest/src/main/java/com/ft546/wcs/shadow/history/visualizer/PointToPointSimulationRequest.java` +- Create: `javaTest/src/main/java/com/ft546/wcs/shadow/history/visualizer/PointToPointSimulationService.java` +- Test: `javaTest/src/test/java/com/ft546/wcs/shadow/history/visualizer/PointToPointSimulationServiceTest.java` + +**Interfaces:** +- Consumes: `HistoricalSimulationInput`, `HistoricalDispatchSimulator`, `SimulationConfig`, and `DeterministicFaultPlan`. +- Produces: `PointToPointSimulationInputFactory.create(HistoricalSimulationInput, String, String, DispatchTask.TaskType, int, String, List)` and `PointToPointSimulationService.run(PointToPointSimulationRequest)`. + +- [ ] **Step 1: Write the failing service tests** + +```java +@Test +void runsOneTaskWithTheRealPlannerAndIgnoresHistoricalPath() { + HistoricalSimulationInput base = loadBaseInput(); + HistoricalSimulationTask sample = base.getTasks().get(0); + PointToPointSimulationRequest request = PointToPointSimulationRequest.valid( + sample.getPhysicalSource(), sample.getPhysicalTarget(), "INBOUND", 1, 100, + "HISTORY-CAR", Arrays.asList("WRONG", "REFERENCE"), "NONE"); + + HistoricalDispatchSimulationResult result = service(base).run(request); + SimulationTaskResult task = result.getTaskResults().get(0); + + assertEquals(SimulationTaskStatus.COMPLETED, task.getStatus()); + assertEquals(sample.getPhysicalSource(), task.getPlannedPath().get(0)); + assertEquals(sample.getPhysicalTarget(), + task.getPlannedPath().get(task.getPlannedPath().size() - 1)); + assertFalse(task.getPlannedPath().equals(Arrays.asList("WRONG", "REFERENCE"))); + assertEquals(0, result.getInvariants().totalSafetyViolations()); +} + +@Test +void rejectsUnknownAndIdenticalEndpointsBeforeSimulation() { + assertThrows(IllegalArgumentException.class, () -> service(base).run( + PointToPointSimulationRequest.valid("UNKNOWN", validSite, "INBOUND", 1, + 100, null, Collections.emptyList(), "NONE"))); + assertThrows(IllegalArgumentException.class, () -> service(base).run( + PointToPointSimulationRequest.valid(validSite, validSite, "INBOUND", 1, + 100, null, Collections.emptyList(), "NONE"))); +} +``` + +- [ ] **Step 2: Run the focused test and confirm it fails** + +Run: + +```bash +mvn "-Dtest=PointToPointSimulationServiceTest" test +``` + +Expected: test compilation fails because the visualizer request/service/factory do not exist. + +- [ ] **Step 3: Implement the request and package-local input factory** + +The factory creates one `HistoricalQueueRecord` and one `HistoricalSimulationTask` with ID +`VISUALIZER-TASK-1`, current fixed simulation time, request business fields, and the base input's map, +traffic map, and initial vehicles. It validates that both sites exist and differ. + +```java +public HistoricalSimulationInput create(HistoricalSimulationInput base, + String sourceSite, + String targetSite, + DispatchTask.TaskType taskType, + int priority, + String historicalVehicleId, + List historicalPath) { + // Construct the comparison record and one simulation task here, inside the + // simulation package where package-private constructors are intentionally visible. +} +``` + +`PointToPointSimulationRequest` normalizes task type, acceleration, empty comparison fields, and +fault preset. It rejects priority outside `0..9`, acceleration other than `10/100`, and unknown enums. + +- [ ] **Step 4: Implement fault presets and run the real simulator** + +```java +public HistoricalDispatchSimulationResult run(PointToPointSimulationRequest request) { + HistoricalSimulationInput input = inputFactory.create(/* normalized request fields */); + return simulator.simulate(input, + new SimulationConfig(request.getAcceleration(), 30_000L, 3, 200, 5_000), + faultPlanFor(request.getFaultPreset())); +} +``` + +`PLC_FIRST_SEND_FAILURE` injects `SEND_FAILURE` on attempt 1 for `VISUALIZER-TASK-1`. +Vehicle presets affect all initial vehicles at tick 0 and restore/connect/synchronize at tick 2 so the +selected task actually demonstrates the requested recovery path. + +- [ ] **Step 5: Run focused tests and commit** + +Run: + +```bash +mvn "-Dtest=PointToPointSimulationServiceTest" test +``` + +Expected: all focused tests pass. + +Commit: + +```bash +git add javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/PointToPointSimulationInputFactory.java javaTest/src/main/java/com/ft546/wcs/shadow/history/visualizer javaTest/src/test/java/com/ft546/wcs/shadow/history/visualizer/PointToPointSimulationServiceTest.java +git commit -m "feat(javaTest): add point-to-point simulation service" +``` + +--- + +### Task 2: Visualization DTO Mapping + +**Files:** +- Create: `javaTest/src/main/java/com/ft546/wcs/shadow/history/visualizer/SimulationVisualizationMapper.java` +- Test: `javaTest/src/test/java/com/ft546/wcs/shadow/history/visualizer/SimulationVisualizationMapperTest.java` + +**Interfaces:** +- Consumes: `HistoricalSimulationInput` and `HistoricalDispatchSimulationResult`. +- Produces: `Map bootstrap(HistoricalSimulationInput)` and `Map result(HistoricalSimulationInput, HistoricalDispatchSimulationResult)`. + +- [ ] **Step 1: Write failing mapping tests** + +```java +@Test +void mapsSitesTasksEventsAndSafetyEvidenceWithoutReplanning() { + Map payload = mapper.result(input, result); + List> tasks = castList(payload.get("tasks")); + List> events = castList(payload.get("events")); + + assertEquals(result.getTaskResults().get(0).getPlannedPath(), tasks.get(0).get("plannedPath")); + assertTrue(events.stream().anyMatch(event -> "ASSIGN".equals(event.get("eventType")))); + assertTrue(events.stream().anyMatch(event -> "MOVE".equals(event.get("eventType")))); + assertEquals(0, castMap(payload.get("invariants")).get("totalSafetyViolations")); +} + +@Test +void bootstrapReturnsSortedSearchableSitesAndInitialVehicles() { + Map payload = mapper.bootstrap(input); + assertFalse(castList(payload.get("sites")).isEmpty()); + assertEquals("OFFLINE_IN_MEMORY_SIMULATION", payload.get("mode")); +} +``` + +- [ ] **Step 2: Run the mapping tests and confirm failure** + +Run `mvn "-Dtest=SimulationVisualizationMapperTest" test`. + +Expected: compilation failure because `SimulationVisualizationMapper` does not exist. + +- [ ] **Step 3: Implement stable ordered JSON maps** + +Use `LinkedHashMap` and sorted task/site/event lists. Include: + +```text +bootstrap: mode, boundary, plannerVersion, mapSha256, floors, sites, vehicles +result: summary, tasks, events, invariants, archive, wms +task: identity, endpoints, vehicles, historicalPath, plannedPath, status, timing, holds, retries +event: tick, simulationMillis, eventType, taskId, vehicleId, reason, fromSite, toSite +site: siteId, floor, x, y, channel +``` + +Do not call `GridPathPlanner` from the mapper. The mapper copies the simulator result unchanged. + +- [ ] **Step 4: Run mapper and existing simulator tests** + +```bash +mvn "-Dtest=SimulationVisualizationMapperTest,HistoricalDispatchSimulatorTest" test +``` + +Expected: all selected tests pass. + +- [ ] **Step 5: Commit** + +```bash +git add javaTest/src/main/java/com/ft546/wcs/shadow/history/visualizer/SimulationVisualizationMapper.java javaTest/src/test/java/com/ft546/wcs/shadow/history/visualizer/SimulationVisualizationMapperTest.java +git commit -m "feat(javaTest): map simulation evidence for visualization" +``` + +--- + +### Task 3: Local HTTP Server and APIs + +**Files:** +- Create: `javaTest/src/main/java/com/ft546/wcs/shadow/history/visualizer/HistoricalDispatchVisualizerApplication.java` +- Create: `javaTest/src/main/java/com/ft546/wcs/shadow/history/visualizer/HistoricalDispatchVisualizerServer.java` +- Create: `javaTest/src/main/java/com/ft546/wcs/shadow/history/visualizer/VisualizerHttpException.java` +- Test: `javaTest/src/test/java/com/ft546/wcs/shadow/history/visualizer/HistoricalDispatchVisualizerServerTest.java` + +**Interfaces:** +- Consumes: Task 1 services and Task 2 mapper. +- Produces: `start()`, `stop()`, `getPort()`, `/api/health`, `/api/bootstrap`, `/api/simulations/point-to-point`, `/api/simulations/historical`, and static asset responses. + +- [ ] **Step 1: Write failing HTTP contract tests** + +```java +@Test +void servesHealthBootstrapAndPointToPointSimulation() throws Exception { + server = fixture.startOnPort(0); + assertEquals(200, get("/api/health").status); + assertTrue(get("/api/bootstrap").body.contains("OFFLINE_IN_MEMORY_SIMULATION")); + + HttpResult response = postJson("/api/simulations/point-to-point", validRequestJson()); + assertEquals(200, response.status); + assertTrue(response.body.contains("plannedPath")); + assertTrue(response.body.contains("MOVE")); +} + +@Test +void rejectsUnknownRoutesMethodsOversizedBodiesAndConcurrentRuns() { + assertEquals(404, get("/api/unknown").status); + assertEquals(405, postJson("/api/health", "{}").status); + assertEquals(413, postJson("/api/simulations/point-to-point", oversizedBody()).status); +} +``` + +- [ ] **Step 2: Run the server tests and confirm failure** + +Run `mvn "-Dtest=HistoricalDispatchVisualizerServerTest" test`. + +Expected: compilation failure because the server classes do not exist. + +- [ ] **Step 3: Implement same-origin JSON handlers** + +Use a fixed thread pool with a single simulation executor and an `AtomicBoolean` busy gate. Parse and +write JSON only through the existing Jackson `ObjectMapper`. Limit request bodies before parsing. + +```java +private void writeJson(HttpExchange exchange, int status, Object payload) throws IOException { + byte[] body = objectMapper.writeValueAsBytes(payload); + exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8"); + exchange.getResponseHeaders().set("Cache-Control", "no-store"); + exchange.sendResponseHeaders(status, body.length); + exchange.getResponseBody().write(body); +} +``` + +Map request failures to stable codes and never return stack traces or absolute paths. + +- [ ] **Step 4: Implement the application bootstrap** + +Load `shadow/history` with `HistoricalQueueLoader`, create the base input once, create services and mapper, +bind `127.0.0.1:`, print the exact URL, and add a shutdown hook. + +- [ ] **Step 5: Run HTTP tests and commit** + +Run `mvn "-Dtest=HistoricalDispatchVisualizerServerTest" test`. + +Expected: all HTTP contract tests pass and test server threads terminate. + +Commit: + +```bash +git add javaTest/src/main/java/com/ft546/wcs/shadow/history/visualizer javaTest/src/test/java/com/ft546/wcs/shadow/history/visualizer/HistoricalDispatchVisualizerServerTest.java +git commit -m "feat(javaTest): expose local dispatch visualizer API" +``` + +--- + +### Task 4: Operational HTML Shell and Route Playback + +**Files:** +- Create: `javaTest/src/main/resources/visualizer/index.html` +- Create: `javaTest/src/main/resources/visualizer/assets/app.css` +- Create: `javaTest/src/main/resources/visualizer/assets/app.js` +- Modify: `javaTest/src/main/java/com/ft546/wcs/shadow/history/visualizer/HistoricalDispatchVisualizerServer.java` +- Test: `javaTest/src/test/java/com/ft546/wcs/shadow/history/visualizer/HistoricalDispatchVisualizerServerTest.java` + +**Interfaces:** +- Consumes: `/api/bootstrap` and both simulation APIs. +- Produces: a responsive first-screen audit console with stable DOM IDs used by browser QA. + +- [ ] **Step 1: Extend static resource tests and confirm failure** + +```java +@Test +void servesTheVisualizerShellAndAssets() throws Exception { + assertEquals(200, get("/").status); + assertTrue(get("/").body.contains("调度路径审计台")); + assertEquals("text/css; charset=utf-8", get("/assets/app.css").contentType); + assertEquals("application/javascript; charset=utf-8", get("/assets/app.js").contentType); +} +``` + +Run `mvn "-Dtest=HistoricalDispatchVisualizerServerTest#servesTheVisualizerShellAndAssets" test`. + +Expected: 404 or missing resource failure. + +- [ ] **Step 2: Build the semantic HTML shell** + +Create fixed sections with IDs: + +```html +
...
+ +
...
+
+
+ +
+
...
+ + +``` + +The page starts as the usable tool, with no hero, marketing copy, nested cards, or decorative illustration. + +- [ ] **Step 3: Implement responsive operational styling** + +Use stable grid tracks, maximum 8px radius, zero negative letter spacing, and no viewport-scaled font size. +Desktop keeps the route stage dominant; under 900px it stacks controls, route, inspector, and timeline. +Use neutral surfaces plus green, blue, amber, and red semantics. + +- [ ] **Step 4: Implement API state, route drawing, and tick playback** + +`app.js` must: + +```text +bootstrap -> populate searchable A/B datalists and metadata +run point task -> store result -> select task -> render path -> reset tick to 0 +render path -> use returned site x/y/floor; draw approach, planned, and traversed layers +advance tick -> update vehicle marker, event focus, traversed edges, inspector, slider +play/pause/step -> use fixed timer and never mutate result data +``` + +Path coordinates use deterministic normalization with fixed SVG padding. Empty/unknown paths render an +explicit status panel instead of a blank SVG. + +- [ ] **Step 5: Implement historical and safety modes** + +Historical mode runs the 240-task API on command, renders KPI bands and a searchable/filterable task table, +and opens the selected task in the shared path/timeline renderer. Safety mode renders every invariant, +HOLD/retry reason distribution, archive count, and WMS idempotency. Any non-zero safety invariant changes the +global gate to `验收不通过`. + +- [ ] **Step 6: Run server tests and commit** + +Run: + +```bash +mvn "-Dtest=HistoricalDispatchVisualizerServerTest" test +``` + +Expected: all static and API tests pass. + +Commit: + +```bash +git add javaTest/src/main/resources/visualizer javaTest/src/main/java/com/ft546/wcs/shadow/history/visualizer/HistoricalDispatchVisualizerServer.java javaTest/src/test/java/com/ft546/wcs/shadow/history/visualizer/HistoricalDispatchVisualizerServerTest.java +git commit -m "feat(javaTest): add interactive dispatch audit console" +``` + +--- + +### Task 5: Documentation, Full Regression, and Browser Acceptance + +**Files:** +- Modify: `javaTest/README.md` +- Create: `javaTest/src/test/resources/visualizer/point-to-point-request.json` +- Create: `javaTest/visualizer-playwright.spec.js` only if the workspace Playwright runner requires a checked-in script; otherwise keep QA scripts in a temporary workspace path. + +**Interfaces:** +- Consumes: completed visualizer application. +- Produces: reproducible launch instructions, Java regression evidence, and desktop/mobile browser evidence. + +- [ ] **Step 1: Document launch and safety boundary** + +Add the exact Maven command, URL, three modes, route color legend, and statement that this tool is offline +evidence rather than production/HIL acceptance. + +- [ ] **Step 2: Run all Java tests** + +```bash +mvn clean test +``` + +Expected: Surefire discovers the existing 630 tests plus all new tests; Failures `0`, Errors `0`. + +- [ ] **Step 3: Start the visualizer server** + +```bash +mvn compile exec:java \ + -Dexec.mainClass=com.ft546.wcs.shadow.history.visualizer.HistoricalDispatchVisualizerApplication \ + -Dexec.args="18084" +``` + +Expected: process remains running and prints `http://127.0.0.1:18084`. + +- [ ] **Step 4: Run Playwright desktop acceptance** + +At `1440x900`: + +```text +open page -> bootstrap succeeds -> select known source/target -> run +assert selected vehicle and planned path are visible +assert SVG has path segments and a vehicle marker +step tick -> assert marker/traversed path changes +run 240 history -> assert 240 completed, 0 waiting, 0 failed +open first task -> assert its route and events render +open safety -> assert totalSafetyViolations is 0 and WMS idempotent is true +assert browser console has no errors +save screenshot +``` + +- [ ] **Step 5: Run Playwright mobile and canvas-pixel acceptance** + +At `390x844`, verify no overlap or horizontal overflow and save a screenshot. Sample pixels in the SVG route +area to prove the visualization is nonblank and contains multiple semantic colors. + +- [ ] **Step 6: Run formatting and repository checks** + +```bash +git diff --check +git status --short +``` + +Expected: no whitespace errors; only intentional visualizer, test, README, spec, and plan changes are present. + +- [ ] **Step 7: Commit the verified result** + +```bash +git add javaTest/README.md javaTest/src/main javaTest/src/test javaTest/src/main/resources/visualizer +git commit -m "docs(javaTest): document dispatch visualizer workflow" +``` + +Do not stage unrelated `ruoyi-admin` or `docs/*.html` changes.