### Summary of Changes
1. Documentation Consolidation:
• Relocated the docs/ folder and all repository .md files (README.md, DEPLOYMENT.md, DOCKER_README.md,
DOMAINS_README.md, GETTING_STARTED.md, CI_CD_PORTAINER_GUIDE.md, ADMIN_DEPLOYMENT_SUMMARY.md,
SECURITY_PRIVATE_FILES.md, handoff.md, azure-project-plan.md, plus service-specific READMEs) into .
• Updated document links in README.md.
2. AI Agent Project Map:
• Created PROJECT_MAP.md in the project root containing complete architecture diagrams, service specs, ports, DB
schemas, environment variables, and developer workflows.
• Added PROJECT_MAP.md to .gitignore.
3. Monitoring UI & Infrastructure Integration:
• Added monitoring_ui service to docker-compose.yml (port 5175).
• Configured Gitea workflow docker-build.yml for monitoring_ui builds.
• Added CORS middleware headers in index.js.
• Added nginx.conf copy step in Dockerfile.
──────
### Recommended Commit Message
docs: consolidate project documentation into monitoring_ui and add agent project map
- Move all markdown documentation files and docs/ folder into monitoring_ui/docs/
- Update documentation hub index (monitoring_ui/docs/README.md) with relative paths
- Generate comprehensive AI agent reference map (PROJECT_MAP.md) in repository root
- Add PROJECT_MAP.md and monitoring_ui build output/env overrides to .gitignore
- Integrate monitoring_ui service into docker-compose.yml and Gitea CI workflow
- Configure CORS handling in employee_api for monitoring dashboard access
──────
### Alternative Short Commit Message
refactor(docs): move docs to monitoring_ui and create AI agent PROJECT_MAP.md
20 KiB
Important: This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
Frontend Steps
Detailed sub-steps for generating the standalone frontend. Read during Step 1 (Frontend).
Companion contract: Before writing any JSX, also read frontend-quality-bar.md. It defines the load-bearing contract between the plan's Section 5 (Design System & UI) and the JSX you ship — per-library region-token → primitive mapping, theming via the library's brand ramp, real icons, and the four-state coverage gate. The sub-steps below cover how to generate and build the frontend (working directory, build, verify); the quality bar covers what the frontend must contain. Design approval already happened during planning via
.azure/.preview-temp/— Step 1 does not re-prompt the user for UX approval.
⚠️ ️ WORKING DIRECTORY — READ BEFORE RUNNING ANY COMMAND
Every frontend command (scaffolder, npm install, npx vite build, npm run build, etc.) MUST be invoked with cwd set to the frontend project folder — the directory that contains the frontend's package.json and index.html (typically services/web/). Running these commands from the workspace root is the #1 cause of build failures.
| ✅ Correct | ❌ Wrong |
|---|---|
run_in_terminal with cwd: "services/web" and command npx vite build |
run_in_terminal from workspace root running npx vite build |
run_in_terminal with cwd: "services/web" and command npm install |
cd services/web && npx vite build chained from another shell that was launched at root, then forgotten on the next command |
Pass the folder explicitly every time (don't rely on a previous cd) |
Assume the terminal is still in services/web/ from an earlier command — the agent runs each command in a fresh shell |
Rules:
- Prefer a working-directory-independent command for the frontend's own npm scripts:
npm --prefix <frontend-folder> run <script>(e.g.npm --prefix services/web run build).--prefixloads the frontend'spackage.jsonfrom that folder no matter where the shell starts, so it cannot accidentally run from the workspace root. - When you must use a tool/binary directly (e.g.
npx vite build), pass the frontend folder via thecwdparameter ofrun_in_terminalon the same call. Do not rely on a previouscd— each terminal invocation may start at the workspace root. - If the tool doesn't take a
cwdparameter, prefix every command withcd <frontend-folder> &&and put it on the same shell call as the build command. Never splitcdandnpx vite buildacross two separaterun_in_terminalcalls. - The frontend folder is whatever the plan specifies — usually
services/web/. Confirm by checking that the folder contains bothpackage.jsonandindex.html(orvite.config.*).
⚠️ Preview compatibility — the "Approve UI" iframe MUST be able to load your dev server
After scaffolding, the flow opens an Approve UI preview (the open_frontend_preview_view tool). That tool starts your frontend's dev server for you and renders it inside a VS Code webview iframe; the user clicks Approve UI there to continue to integration. If that iframe can't load the running app, the user is stuck — the app can be "live" in a normal browser window yet the preview shows a blank surface or never leaves "Starting…", and Approve UI never enables. Scaffold the frontend so the preview can always load it:
- Make the dev server embeddable + reachable. In
vite.configsetserver: { host: true, allowedHosts: true, strictPort: false }(Angular:ng serve --host 0.0.0.0 --disable-host-check; Next.js:next dev -H 0.0.0.0).host: truelets the webview / forwarded port (remote, Codespaces, Dev Container, SSH) reach it;allowedHosts: truestops the dev server 403-blocking the forwarded/webview origin;strictPort: falselets the preview bind a free port instead of erroring when the default is taken. See architecture.md → Frontend Dev Server Configuration. - Keep the
dev/startscript a real server. It must launch a server that printshttp://localhost:<port>/(plainvite,next dev,ng serve) — nevervite build --watchor a build-only script. The preview waits for that URL before it enables Approve UI; a script that never serves leaves it on "Starting…" until it times out. - Never frame-bust the dev server. No
X-Frame-Optionsheader from the dev server and no<meta http-equiv="Content-Security-Policy" content="… frame-ancestors …">inindex.html. Those let a top-level browser tab load the app but block embedding in the webview iframe — the exact "works in my browser, blank in the preview" trap. - Do not run a competing dev server. The
open_frontend_preview_viewtool owns the single dev server; the scaffold only builds (npm run build, per F4). Do not start your ownnpm run dev, and do not scaffold a VS Code task that auto-starts the dev server on folder open — a second server contends for the port, so the preview's own server can fail to bind and error out while the other server is what shows "live" in the browser.
Sub-step F1: Initialize Frontend Project
| Task | Details |
|---|---|
| Initialize frontend project | React + Vite / Vue + Vite / Angular / Svelte (per plan) |
| Make the dev server preview-embeddable | In vite.config set server: { host: true, allowedHosts: true, strictPort: false } (Angular: ng serve --host 0.0.0.0 --disable-host-check; Next.js: next dev -H 0.0.0.0). Keep the dev script a plain server (vite), and keep X-Frame-Options / frame-ancestors CSP out of index.html. This is what lets the Approve UI preview load your app in its webview iframe (the /api proxy is added later by the integrate agent). See the Preview compatibility callout above. |
| Create the frontend folder | Use the path the plan specifies (e.g. services/web/); follow the user's existing structure when one exists. Standard structure matching plan's frontend framework |
| Create local type definitions | Define entity types locally in the frontend's types folder (e.g. services/web/src/types/) — standalone mock types for now |
Define the ApiClient interface |
In the seam folder services/web/src/api/types.ts, declare an ApiClient interface with one method per endpoint in the plan's API route inventory (Section 7), named and typed from the route contract (e.g. listItems(): Promise<Item[]>, getItem(id: string): Promise<Item>, createItem(input: CreateItemRequest): Promise<Item>). Then add a method for every other entity any page, component, or provider renders, even when the plan has no route for it — assignee/author lookups, the signed-in user, category or status reference lists (e.g. listUsers(): Promise<User[]>, getCurrentUser(): Promise<User>). When API Login is Yes, include the auth methods the UI needs, including createAccount(input), even though the plan omits auth routes. Reference data still goes through the seam; the mock backs it at scaffold time and the integrate agent maps it to a real route or a static list later. The interface must cover everything the UI renders — a page that needs data the interface does not expose has no legal way to get it and will be forced to reach around the seam. This interface is the stable seam both the mock and the future live client implement — it is what makes integration a one-file swap. |
Sub-step F2: Create the Mock Data Layer Behind the src/api/ Seam
Load-bearing seam rule. No file outside
services/web/src/api/imports the mock — not pages, not hooks, not shared components, not the auth provider. They all import a singleapiobject fromservices/web/src/api/(the seam). At scaffold time thatapiis backed by the mock implementation; at integrate time the integrate agent swaps one file (src/api/index.ts) to point at the live client and deletes the mock impl — with no edits anywhere outsidesrc/api/. Anything that importedsrc/mocks/directly stops compiling the moment the mock is deleted, which is exactly what turns the one-file swap into a rewrite.If a file needs data the
ApiClientinterface does not expose — including reference data the plan has no route for — add a method to the interface (F1) and back it with the mock. Never import the fixture. This case is not an exemption from the rule and it is not a judgement call: extend the seam. Wire the seam exactly as below so the swap stays a single-file edit.
| Task | Details |
|---|---|
| Create mock data files | services/web/src/mocks/data.ts — realistic sample data matching plan entities. Any entity field that represents an image — image/photo/avatar/cover/thumbnail/banner/url on a media entity — MUST be populated with a real, loadable image URL, never left blank, null, or pointing at a solid-color placeholder. Use https://picsum.photos/seed/<stable-id>/<w>/<h> for generic media, curated https://images.unsplash.com/... URLs for domain-specific imagery, and https://i.pravatar.cc/<size>?u=<id> for avatars. Empty media surfaces render as flat color blocks and fail the quality bar. |
Create the mock client (an ApiClient impl) |
services/web/src/api/mockClient.ts — export const mockClient: ApiClient = { … } implementing every method of the ApiClient interface from F1 (including the reference-data methods), returning data from src/mocks/data.ts with small simulated delays. It MUST satisfy the interface so it is type-interchangeable with the future live client. Declare the mock as ApiClient; never derive the interface from the mock. export type ApiClient = typeof mockClient inverts the seam — integrate deletes the mock, which deletes the interface with it and leaves the live client nothing to implement. |
| Create the seam entry (the one file that swaps) | services/web/src/api/index.ts — the single swap point. It contains exactly:import type { ApiClient } from './types';import { mockClient } from './mockClient';export const api: ApiClient = mockClient;export type { ApiClient } from './types';At integrate time only this file changes (mock → live). Keep it this small — no logic, just the wiring line. |
| Auto-seed auth state | When the plan says API Login: Yes, the local auth state MUST auto-login with local credentials on first load (no token in storage). Preview boots directly into the main authenticated view, not a login page. Login/logout MUST still work if the user manually logs out. Get the signed-in user from the seam (api.getCurrentUser(), added to the interface in F1). The auth provider follows the seam rule exactly like a page, so import { currentUser } from '../mocks/data' is a violation. When API Login: No, do not scaffold auth state or login UI. |
| Build the Mock State Switcher (STANDARD — always) | Create services/web/src/api/previewState.ts exposing the forced PreviewDataState ('data' | 'loading' | 'empty' | 'error'), initialized from the ?previewState= query param → localStorage['previewState'] → 'data'. The mock client must honor it on every method: loading → never/slowly resolves; error → rejects with a realistic Error; empty → returns [] / null; data → normal fixtures. Render a small fixed-corner Data/Loading/Empty/Error switcher gated by import.meta.env.DEV (forced to 'data', no UI, in PROD). This is a fixed contract — scaffold it the same way every time. See the Mock State Switcher standard in frontend-quality-bar.md. |
| Handle all 4 data states | Loading (skeleton/spinner), Error (retry button), Empty (call-to-action), Data (populated) — all four reachable live via the Mock State Switcher above |
Sub-step F3: Create Pages & Components
| Task | Details |
|---|---|
| Create pages | One page per major feature, wired to the api object from the seam — import { api } from '@/api' (or the relative ../api). Never import from src/mocks/ or src/api/mockClient.ts in ANY file outside src/api/ — not a page, not a hook, not a shared component, not the auth provider. Need an assignee name, the signed-in user, or any other reference data? Add an ApiClient method for it (F1) and call it through the seam. This is what keeps integration a one-file swap. |
| Create shared components | Reusable UI components (layout, nav, forms, cards) |
| Error handling in hooks | Every async hook catches errors, handles loading/error states |
| Destructive action confirmations | Delete and irreversible actions require user confirmation |
| Auth context auto-login | When API Login is Yes, AuthProvider/auth context MUST auto-login on mount when no token exists, so preview opens to main authenticated content |
| Account creation | When API Login is Yes, the login page MUST show a visible Create account button that routes to a dedicated create-account page. The page calls api.createAccount(...) through the seam and displays validation, duplicate-account, loading, and success states. This is required even though local auto-login makes the main authenticated content the initial preview. |
| Use correct file extensions | .tsx for JSX, .ts for pure TypeScript |
Sub-step F4: Build & Verify the Frontend
⚠️ PARALLEL STEP: Frontend generation + build (F1–F4, sub-agent) runs concurrently with Phase A (Contracts) and Phase B (Backend). Backend derives from plan's route definitions and entity types, not the frontend — independent work streams. Phase A and Phase B may begin immediately after Step 0 (plan validation) while the Frontend sub-agent generates and builds
services/web/.The Frontend sub-agent only needs to generate and build
services/web/with mock data — it does not wire to the real backend. The verify agent, in a later session, swaps the seam (src/api/index.ts) from the mock client to the live client and replaces the local mock types with shared imports — a one-file swap at the seam, no page or hook edits.Why safe: Entity types, route definitions, service interfaces all come from approved plan. Frontend uses standalone mock types (
services/web/src/types/) independent ofservices/shared/, behind theApiClientseam (src/api/). Frontend UI changes (layout, styling, components) don't affect backend contracts. The verify agent merges both streams later by repointing the seam at the live client and replacing mock types with shared imports.
⚠️ ️ WORKING DIRECTORY (see also the top of this file): every
npx vite build,npm run build,npm install, etc. MUST run against the frontend folder (e.g.services/web/), never the workspace root. Prefer the working-directory-independent formnpm --prefix services/web run <script>(e.g.npm --prefix services/web run build) — it loads the frontend'spackage.jsonregardless of where the shell starts. When invoking a binary directly (npx vite build), passcwd: services/webon the same terminal call; do not assume a previouscdcarried over.
⚠️ NO UX APPROVAL PROMPT. The user already approved the design during planning via the HTML/CSS mock-up at
.azure/.preview-temp/. Do NOT callask_userfor "do you approve this UI?" during scaffolding. The only legitimate user prompt during Step 1 is a hard build failure that requires their input to resolve.
Procedure
- Frontend builds with zero errors. Build with a working-directory-independent command so it can't accidentally run from the workspace root:
npm --prefix <frontend-folder> run build(e.g.npm --prefix services/web run build).--prefixresolves the frontend'spackage.jsonregardless of where the shell starts, so it's immune to the root-launch bug. Only fall back tonpx vite buildwithcwd: <frontend-folder>if there is nobuildscript. Never run a barenpx vite buildfrom the project root. - No
anytypes in.ts/.tsxfiles. - When
API LoginisYes, local identity state is seeded so the app lands on main content, not a login page, on first load. Logging out exposes a login page with a visible Create account button and a working dedicated create-account page. When it isNo, the frontend contains no auth UI or state. - Briefly note that the frontend was generated and builds cleanly, and that backend work continues in parallel — one short sentence. Then keep working — no approval question, no waiting loop.
CRITICAL: Do NOT prompt "Would you like to preview?" or "Do you approve this UI?" during scaffolding — design approval already happened during planning via the HTML mock-up.
Translating the planning mock-up into real framework code
The .azure/.preview-temp/*.html files are a layout + tonal reference, not source code to ship. For each page:
- The plan's Pages table tells you which regions belong on the page (
header + hero + grid + footeretc.). - The HTML mock-up shows the approved arrangement, density, and palette of those regions.
- Your job in Step 1 is to reproduce that visual feel using the real
Component Library:primitives from Section 6 (see frontend-quality-bar.md for the per-library token → primitive mapping).
Do not import the HTML mock-up, embed it via <iframe>, or copy CSS class names from it into your JSX — the mock-up is throwaway and .azure/.preview-temp/ is deleted in Step 11. Only the visual intent (regions, palette, density) carries forward into real components.
Frontend Quality Bar
Even before it's wired to the backend, the frontend MUST meet these standards. The full per-library contract lives in frontend-quality-bar.md — read it before writing any JSX. Baseline rules enforced here:
- No
anytypes (use local type definitions inservices/web/src/types/) - Hooks catch errors and handle loading/error states
- Destructive actions (delete, etc.) require
window.confirm()before executing .tsxfor files containing JSX,.tsfor pure TypeScript- All 4 data states handled: loading, error, empty, data (see quality-bar's State Coverage Contract for per-library primitives —
<Skeleton>/<MessageBar intent="error">/ empty illustration + CTA / real data), and all four reachable live via the Mock State Switcher (dev-only?previewState=override — see quality-bar's Mock State Switcher standard) - Auto-authenticated preview: When
API LoginisYes, local auth state MUST auto-login on first load so the user sees main content immediately, not a login page - Render layout tokens with real library primitives — never raw
<div className="card">placeholders. See frontend-quality-bar.md for the region-token → primitive mapping per library. - Wrap the app shell in the library's theme provider with a brand ramp derived from plan Section 6's
primarycolor. See frontend-quality-bar.md → Theming contract. - Use real icons from the library's icon set (Fluent:
@fluentui/react-iconsRegular variants; Vuetify:mdi-*; Material:<mat-icon>real names; Skeleton/Pico: Lucide/Tabler). No emoji, no SVG placeholders.