# Web Page Split And Root Source Cleanup > **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:** Restructure the `web/` directory into `core/` + `feeder/` + `ops/` subdirectories, delete the obsolete root `src/` files, and update README to reflect the new workspace layout. **Architecture:** Web pages split into a shared `web/core/` (platform HTML partials and CSS) and per-app directories (`web/feeder/`, `web/ops/`). Each app's Axum router uses `ServeDir` with fallback: try app-specific dir first, then core. This means no URL changes in HTML/JS — the fallback chain resolves transparently. The root `src/` contains stale copies of files already migrated to crates and must be removed. **Tech Stack:** Rust (Axum, tower-http ServeDir), HTML/CSS/JS (vanilla, ES modules), Cargo workspace --- ## File Map ### Web core (shared platform pages) - Move: `web/styles.css` → `web/core/styles.css` - Move: `web/html/source-panel.html` → `web/core/html/source-panel.html` - Move: `web/html/points-panel.html` → `web/core/html/points-panel.html` - Move: `web/html/equipment-panel.html` → `web/core/html/equipment-panel.html` - Move: `web/html/chart-panel.html` → `web/core/html/chart-panel.html` - Move: `web/html/log-stream-panel.html` → `web/core/html/log-stream-panel.html` - Move: `web/html/logs-panel.html` → `web/core/html/logs-panel.html` - Move: `web/html/api-doc-drawer.html` → `web/core/html/api-doc-drawer.html` - Create: `web/core/html/modals.html` (core modals only — equipment, source, point, binding; unit modal removed) ### Web feeder (feeder-specific pages + all JS) - Move: `web/index.html` → `web/feeder/index.html` (add unit-modal partial reference) - Move: `web/html/topbar.html` → `web/feeder/html/topbar.html` - Move: `web/html/ops-panel.html` → `web/feeder/html/ops-panel.html` - Create: `web/feeder/html/unit-modal.html` (extracted from old modals.html) - Move: `web/js/*.js` → `web/feeder/js/*.js` (all 15 JS files stay together as interconnected module graph) ### Web ops (operation-system pages) - Move: `crates/app_operation_system/web/index.html` → `web/ops/index.html` (updated content) - Create: `web/ops/html/topbar.html` - Create: `web/ops/js/index.js` - Create: `web/ops/js/app.js` ### Rust router changes - Modify: `crates/app_feeder_distributor/src/router.rs` (update ServeDir to use fallback) - Modify: `crates/app_operation_system/src/router.rs` (update ServeDir to use fallback) ### Root src cleanup - Delete: all 19 files under `src/` (stale duplicates of files in crates) ### Documentation - Modify: `README.md` --- ## Task 1: Split modals.html And Create Unit Modal Partial **Files:** - Create: `web/core/html/modals.html` - Create: `web/feeder/html/unit-modal.html` - [ ] **Step 1: Create core modals (without unit modal)** Extract everything except the unit modal div from `web/html/modals.html` into a new file: `web/core/html/modals.html`: ```html ``` - [ ] **Step 2: Create feeder unit modal partial** Extract the unit modal into its own file: `web/feeder/html/unit-modal.html`: ```html ``` - [ ] **Step 3: Verify both files contain all original modal content** Check that the combined line count of the two new files matches the original `web/html/modals.html` (188 lines total, minus blank lines between sections). - [ ] **Step 4: Commit** ```bash git add web/core/html/modals.html web/feeder/html/unit-modal.html git commit -m "refactor(web): split modals into core and feeder unit-modal" ``` ## Task 2: Move Core HTML And CSS Into web/core **Files:** - Move: `web/styles.css` → `web/core/styles.css` - Move: `web/html/source-panel.html` → `web/core/html/source-panel.html` - Move: `web/html/points-panel.html` → `web/core/html/points-panel.html` - Move: `web/html/equipment-panel.html` → `web/core/html/equipment-panel.html` - Move: `web/html/chart-panel.html` → `web/core/html/chart-panel.html` - Move: `web/html/log-stream-panel.html` → `web/core/html/log-stream-panel.html` - Move: `web/html/logs-panel.html` → `web/core/html/logs-panel.html` - Move: `web/html/api-doc-drawer.html` → `web/core/html/api-doc-drawer.html` - [ ] **Step 1: Create core directories and move files** ```bash mkdir -p web/core/html git mv web/styles.css web/core/styles.css git mv web/html/source-panel.html web/core/html/source-panel.html git mv web/html/points-panel.html web/core/html/points-panel.html git mv web/html/equipment-panel.html web/core/html/equipment-panel.html git mv web/html/chart-panel.html web/core/html/chart-panel.html git mv web/html/log-stream-panel.html web/core/html/log-stream-panel.html git mv web/html/logs-panel.html web/core/html/logs-panel.html git mv web/html/api-doc-drawer.html web/core/html/api-doc-drawer.html ``` - [ ] **Step 2: Commit** ```bash git add web/core git commit -m "refactor(web): move shared HTML partials and CSS into web/core" ``` ## Task 3: Move Feeder-Specific HTML And All JS Into web/feeder **Files:** - Move: `web/index.html` → `web/feeder/index.html` - Move: `web/html/topbar.html` → `web/feeder/html/topbar.html` - Move: `web/html/ops-panel.html` → `web/feeder/html/ops-panel.html` - Move: `web/js/*.js` → `web/feeder/js/*.js` - Delete: `web/html/modals.html` (replaced by split files in Task 1) - [ ] **Step 1: Create feeder directories and move files** ```bash mkdir -p web/feeder/html web/feeder/js git mv web/html/topbar.html web/feeder/html/topbar.html git mv web/html/ops-panel.html web/feeder/html/ops-panel.html git mv web/js/api.js web/feeder/js/api.js git mv web/js/app.js web/feeder/js/app.js git mv web/js/chart.js web/feeder/js/chart.js git mv web/js/docs.js web/feeder/js/docs.js git mv web/js/dom.js web/feeder/js/dom.js git mv web/js/equipment.js web/feeder/js/equipment.js git mv web/js/events.js web/feeder/js/events.js git mv web/js/index.js web/feeder/js/index.js git mv web/js/logs.js web/feeder/js/logs.js git mv web/js/ops.js web/feeder/js/ops.js git mv web/js/points.js web/feeder/js/points.js git mv web/js/roles.js web/feeder/js/roles.js git mv web/js/sources.js web/feeder/js/sources.js git mv web/js/state.js web/feeder/js/state.js git mv web/js/units.js web/feeder/js/units.js ``` - [ ] **Step 2: Move index.html and delete old modals** ```bash git mv web/index.html web/feeder/index.html git rm web/html/modals.html ``` - [ ] **Step 3: Update feeder index.html to add unit-modal partial** In `web/feeder/index.html`, change the modals partial line and add a unit-modal partial: Before: ```html
``` After: ```html
``` - [ ] **Step 4: Verify no files remain in old web/html and web/js directories** ```bash ls web/html/ 2>/dev/null && echo "ERROR: web/html still has files" || echo "OK: web/html is clean" ls web/js/ 2>/dev/null && echo "ERROR: web/js still has files" || echo "OK: web/js is clean" ``` Expected: both directories are empty or deleted. - [ ] **Step 5: Commit** ```bash git add web/feeder git commit -m "refactor(web): move feeder HTML, JS, and index into web/feeder" ``` ## Task 4: Update Feeder Router To Use Fallback ServeDir **Files:** - Modify: `crates/app_feeder_distributor/src/router.rs` - [ ] **Step 1: Update the static file serving to use fallback chain** In `crates/app_feeder_distributor/src/router.rs`, replace the current `/ui` nest: Before: ```rust .nest( "/ui", Router::new() .fallback_service(ServeDir::new("web").append_index_html_on_directories(true)) .layer(axum::middleware::from_fn(no_cache)), ) ``` After: ```rust .nest( "/ui", Router::new() .fallback_service( ServeDir::new("web/feeder") .append_index_html_on_directories(true) .fallback(ServeDir::new("web/core")), ) .layer(axum::middleware::from_fn(no_cache)), ) ``` - [ ] **Step 2: Verify feeder crate compiles** Run: ```bash cargo check -p app_feeder_distributor ``` Expected: PASS - [ ] **Step 3: Commit** ```bash git add crates/app_feeder_distributor/src/router.rs git commit -m "refactor(feeder): update static file serving for split web dirs" ``` ## Task 5: Create Operation-System Web Pages And Update Router **Files:** - Create: `web/ops/index.html` - Create: `web/ops/html/topbar.html` - Create: `web/ops/js/index.js` - Create: `web/ops/js/app.js` - Modify: `crates/app_operation_system/src/router.rs` - Delete: `crates/app_operation_system/web/index.html` - [ ] **Step 1: Create ops web scaffold** `web/ops/index.html`: ```html 运转系统
运转系统页面开发中
``` `web/ops/html/topbar.html`: ```html
运转系统
连接中…
``` `web/ops/js/index.js`: ```javascript async function loadPartial(slot) { const response = await fetch(slot.dataset.partial); if (!response.ok) { throw new Error(`Failed to load partial: ${slot.dataset.partial}`); } const html = await response.text(); slot.insertAdjacentHTML("beforebegin", html); slot.remove(); } async function bootstrapPage() { const slots = Array.from(document.querySelectorAll("[data-partial]")); await Promise.all(slots.map((slot) => loadPartial(slot))); await import("./app.js"); } bootstrapPage().catch((error) => { document.body.innerHTML = `
${error.message || String(error)}
`; }); ``` `web/ops/js/app.js`: ```javascript function bootstrap() { console.log("Operation system app initialized"); } bootstrap(); ``` - [ ] **Step 2: Update ops router to use split web dirs** Replace `crates/app_operation_system/src/router.rs`: ```rust use axum::{extract::State, routing::get, Router}; use tower_http::services::ServeDir; use crate::app::AppState; async fn no_cache(req: axum::extract::Request, next: axum::middleware::Next) -> axum::response::Response { let mut response = next.run(req).await; response.headers_mut().insert( axum::http::header::CACHE_CONTROL, axum::http::HeaderValue::from_static("no-store"), ); response } pub fn build_router(state: AppState) -> Router { Router::new() .route("/api/health", get(health_check)) .nest( "/ui", Router::new() .fallback_service( ServeDir::new("web/ops") .append_index_html_on_directories(true) .fallback(ServeDir::new("web/core")), ) .layer(axum::middleware::from_fn(no_cache)), ) .with_state(state) } async fn health_check(State(state): State) -> String { format!("{}:ok", state.app_name) } ``` - [ ] **Step 3: Delete old ops web placeholder** ```bash git rm crates/app_operation_system/web/index.html rmdir crates/app_operation_system/web 2>/dev/null || true ``` - [ ] **Step 4: Verify ops crate compiles** Run: ```bash cargo check -p app_operation_system ``` Expected: PASS - [ ] **Step 5: Update ops router smoke test if needed** Check `crates/app_operation_system/tests/router_smoke.rs` — if it references the old `WEB_ROOT` constant, update accordingly. - [ ] **Step 6: Commit** ```bash git add web/ops crates/app_operation_system git commit -m "refactor(ops): add ops web scaffold and update router for split dirs" ``` ## Task 6: Delete Obsolete Root src/ Files **Files:** - Delete: all 19 files under `src/` - [ ] **Step 1: Verify all root src files are duplicates of crate files** Run quick checks: ```bash diff src/config.rs crates/app_feeder_distributor/src/config.rs diff src/handler.rs crates/app_feeder_distributor/src/handler.rs diff src/middleware.rs crates/app_feeder_distributor/src/middleware.rs ``` All should show no functional differences (only BOM or whitespace). - [ ] **Step 2: Remove all root src files from git** ```bash git rm -r src/ ``` - [ ] **Step 3: Verify workspace still builds** Run: ```bash cargo check --workspace ``` Expected: PASS (root src/ is not a workspace member, removing it changes nothing for the build) - [ ] **Step 4: Commit** ```bash git commit -m "chore: remove obsolete root src/ (migrated to crates)" ``` ## Task 7: Update README **Files:** - Modify: `README.md` - [ ] **Step 1: Update the README to reflect the workspace structure** Replace the outdated "后端结构" and add build instructions. Key sections to update: - Remove references to `src/main.rs`, `src/handler`, `src/service` - Add workspace structure overview: ```markdown ## 项目结构 ```text plc_control/ Cargo.toml # Workspace root crates/ plc_platform_core/ # 共享平台核心库 app_feeder_distributor/ # 投煤器布料机专用版 app_operation_system/ # 运转系统专用版 web/ core/ # 共享 HTML/CSS(点位、设备、数据源等) feeder/ # 投煤器布料机页面 + JS ops/ # 运转系统页面 + JS ``` ## 构建 ```powershell # 投煤器布料机 cargo build -p app_feeder_distributor --release # 运转系统 cargo build -p app_operation_system --release ``` ## 部署 将编译产物和 `web/` 目录放在同一级目录下: ```text deploy/ app_feeder_distributor.exe web/ core/ feeder/ ``` ``` - [ ] **Step 2: Commit** ```bash git add README.md git commit -m "docs: update README for workspace and web split layout" ``` ## Task 8: Final Verification - [ ] **Step 1: Run all tests** ```bash cargo test --workspace ``` Expected: PASS - [ ] **Step 2: Run release builds** ```bash cargo build -p app_feeder_distributor --release cargo build -p app_operation_system --release ``` Expected: both produce binaries successfully. - [ ] **Step 3: Verify web file layout** ```bash find web -type f | sort ``` Expected: files organized under `web/core/`, `web/feeder/`, `web/ops/` only. No files remaining directly under `web/html/` or `web/js/`. ## Self-Review ### Spec coverage - Web split into core + per-app directories: Tasks 1–5 - Fallback ServeDir for transparent URL resolution: Tasks 4–5 - Root src cleanup: Task 6 - README update: Task 7 - Build verification: Task 8 ### Key design decision: ServeDir fallback Using `ServeDir::new("web/feeder").fallback(ServeDir::new("web/core"))` means: - No URL changes needed in any HTML partial references or JS imports - App-specific files override core files of the same name (app takes priority) - Browser requests are resolved transparently through the chain ### Spec deviation: web directory location The original design spec §8.4 suggested per-app web directories inside each crate (`app_feeder_distributor/web`, `app_operation_system/web`). This plan deliberately places web files at the workspace root (`web/core/`, `web/feeder/`, `web/ops/`) instead. Reason: enables the ServeDir fallback chain to share core assets without duplication, and avoids coupling web resources to Rust crate build paths. This is a justified departure from the spec. ### What this plan does NOT cover (deferred) - `PlatformContext` completion (filling in pool/connection_manager/event_manager/ws_manager) - `config.rs` migration into shared core - `control/validator.rs` splitting - Event namespace migration at call sites - These are larger refactors that should be planned separately