17 KiB
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:
<div class="modal hidden" id="equipmentModal">
<!-- keep entire equipment modal as-is from current modals.html lines 53-87 -->
</div>
<div class="modal hidden" id="pointModal">
<!-- keep entire point modal as-is from current modals.html lines 89-107 -->
</div>
<div class="modal hidden" id="sourceModal">
<!-- keep entire source modal as-is from current modals.html lines 109-135 -->
</div>
<div class="modal hidden" id="pointBindingModal">
<!-- keep entire binding modal as-is from current modals.html lines 137-163 -->
</div>
<div class="modal hidden" id="batchBindingModal">
<!-- keep entire batch binding modal as-is from current modals.html lines 165-187 -->
</div>
- Step 2: Create feeder unit modal partial
Extract the unit modal into its own file:
web/feeder/html/unit-modal.html:
<div class="modal hidden" id="unitModal">
<!-- keep entire unit modal as-is from current modals.html lines 1-51 -->
</div>
- 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
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
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
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
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
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:
<div data-partial="/ui/html/modals.html"></div>
After:
<div data-partial="/ui/html/modals.html"></div>
<div data-partial="/ui/html/unit-modal.html"></div>
- Step 4: Verify no files remain in old web/html and web/js directories
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
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:
.nest(
"/ui",
Router::new()
.fallback_service(ServeDir::new("web").append_index_html_on_directories(true))
.layer(axum::middleware::from_fn(no_cache)),
)
After:
.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:
cargo check -p app_feeder_distributor
Expected: PASS
- Step 3: Commit
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:
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>运转系统</title>
<link rel="stylesheet" href="/ui/styles.css" />
</head>
<body>
<div data-partial="/ui/html/topbar.html"></div>
<main>
<div class="muted" style="padding:2rem;text-align:center">运转系统页面开发中</div>
</main>
<script type="module" src="/ui/js/index.js"></script>
</body>
</html>
web/ops/html/topbar.html:
<header class="topbar">
<div class="title">运转系统</div>
<div class="topbar-actions">
<div class="status" id="statusText">
<span class="ws-dot" id="wsDot"></span>
<span id="wsLabel">连接中…</span>
</div>
</div>
</header>
web/ops/js/index.js:
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 = `<pre>${error.message || String(error)}</pre>`;
});
web/ops/js/app.js:
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:
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<AppState>) -> String {
format!("{}:ok", state.app_name)
}
- Step 3: Delete old ops web placeholder
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:
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
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:
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
git rm -r src/
- Step 3: Verify workspace still builds
Run:
cargo check --workspace
Expected: PASS (root src/ is not a workspace member, removing it changes nothing for the build)
- Step 4: Commit
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:
## 项目结构
```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
构建
# 投煤器布料机
cargo build -p app_feeder_distributor --release
# 运转系统
cargo build -p app_operation_system --release
部署
将编译产物和 web/ 目录放在同一级目录下:
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
cargo test --workspace
Expected: PASS
- Step 2: Run release builds
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
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)
PlatformContextcompletion (filling in pool/connection_manager/event_manager/ws_manager)config.rsmigration into shared corecontrol/validator.rssplitting- Event namespace migration at call sites
- These are larger refactors that should be planned separately