docs(deploy): session handoff + refresh GitNexus index stats
Handoff summarises the unified deployment page implementation (spec, plan, 43 commits, opened Gitea issues #147 and #148), open gaps, and recommended kickoff for the next session. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
<!-- gitnexus:start -->
|
<!-- gitnexus:start -->
|
||||||
# GitNexus — Code Intelligence
|
# GitNexus — Code Intelligence
|
||||||
|
|
||||||
This project is indexed by GitNexus as **cameleer-server** (8893 symbols, 23049 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
This project is indexed by GitNexus as **cameleer-server** (9095 symbols, 23495 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||||
|
|
||||||
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
|
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ When adding, removing, or renaming classes, controllers, endpoints, UI component
|
|||||||
<!-- gitnexus:start -->
|
<!-- gitnexus:start -->
|
||||||
# GitNexus — Code Intelligence
|
# GitNexus — Code Intelligence
|
||||||
|
|
||||||
This project is indexed by GitNexus as **cameleer-server** (8893 symbols, 23049 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
This project is indexed by GitNexus as **cameleer-server** (9095 symbols, 23495 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||||
|
|
||||||
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
|
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
|
||||||
|
|
||||||
|
|||||||
183
docs/handoff/2026-04-23-deployment-page-handoff.md
Normal file
183
docs/handoff/2026-04-23-deployment-page-handoff.md
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
# Handoff — Unified App Deployment Page
|
||||||
|
|
||||||
|
**Session:** 2026-04-22 → 2026-04-23
|
||||||
|
**Branch:** `main` (43 commits ahead of `origin/main` before push — all committed directly per explicit user consent)
|
||||||
|
**Base commit (session start):** `1a376eb2`
|
||||||
|
**Head commit (session end):** `0a71bca7`
|
||||||
|
|
||||||
|
## What landed
|
||||||
|
|
||||||
|
Full implementation of the unified app deployment page replacing the old `CreateAppView` / `AppDetailView` split. Key artefacts:
|
||||||
|
|
||||||
|
- **Spec:** `docs/superpowers/specs/2026-04-22-app-deployment-page-design.md`
|
||||||
|
- **Plan:** `docs/superpowers/plans/2026-04-22-app-deployment-page.md`
|
||||||
|
- **Routes:** `/apps` (list, unchanged), `/apps/new` + `/apps/:slug` (both render new `AppDeploymentPage`)
|
||||||
|
|
||||||
|
### Backend delivered (cameleer-server)
|
||||||
|
|
||||||
|
- Flyway V3 adds `deployments.deployed_config_snapshot JSONB`
|
||||||
|
- `DeploymentConfigSnapshot` record: `(UUID jarVersionId, ApplicationConfig agentConfig, Map<String,Object> containerConfig, List<String> sensitiveKeys)`
|
||||||
|
- `DeploymentExecutor` captures snapshot on successful RUNNING transition (not FAILED)
|
||||||
|
- `PostgresDeploymentRepository.saveDeployedConfigSnapshot(UUID, DeploymentConfigSnapshot)` + `findLatestSuccessfulByAppAndEnv(appId, envId)`
|
||||||
|
- `ApplicationConfigController.updateConfig` accepts `?apply=staged|live` (default `live` for back-compat); staged skips SSE push; 400 on unknown
|
||||||
|
- `AppController.getDirtyState` → `GET /api/v1/environments/{envSlug}/apps/{appSlug}/dirty-state` returning `{dirty, lastSuccessfulDeploymentId, differences[]}`
|
||||||
|
- `DirtyStateCalculator` pure service (cameleer-server-core), scrubs volatile fields (`version`, `updatedAt`, `updatedBy`, `environment`, `application`) from agent-config comparison, recurses into nested objects
|
||||||
|
- Integration tests: `PostgresDeploymentRepositoryIT` (3), `DeploymentSnapshotIT` (2), `ApplicationConfigControllerIT` (6), `AppDirtyStateIT` (3), `DirtyStateCalculatorTest` (9)
|
||||||
|
- OpenAPI + `schema.d.ts` regenerated
|
||||||
|
|
||||||
|
### UI delivered (cameleer-server/ui)
|
||||||
|
|
||||||
|
New directory `ui/src/pages/AppsTab/AppDeploymentPage/`:
|
||||||
|
|
||||||
|
```
|
||||||
|
index.tsx # Main composition (524 lines)
|
||||||
|
IdentitySection.tsx # Name + slug + env pill + JAR + Current Version
|
||||||
|
Checkpoints.tsx # Collapsible disclosure of past successful deploys
|
||||||
|
PrimaryActionButton.tsx # Save / Redeploy / Deploying… state machine
|
||||||
|
AppDeploymentPage.module.css # Page-local styles
|
||||||
|
ConfigTabs/
|
||||||
|
MonitoringTab.tsx # Engine, payload, log levels, metrics, sampling, replay, route control
|
||||||
|
ResourcesTab.tsx # CPU / memory / ports / replicas / runtime / networks
|
||||||
|
VariablesTab.tsx # Env vars (Table / Properties / YAML / .env via EnvEditor)
|
||||||
|
SensitiveKeysTab.tsx # Per-app keys + global baseline reference
|
||||||
|
TracesTapsTab.tsx # Live-apply with LiveBanner
|
||||||
|
RouteRecordingTab.tsx # Live-apply with LiveBanner
|
||||||
|
LiveBanner.tsx # Shared amber "changes apply immediately" banner
|
||||||
|
DeploymentTab/
|
||||||
|
DeploymentTab.tsx # Composition: StatusCard + DeploymentProgress + StartupLogPanel + History
|
||||||
|
StatusCard.tsx # RUNNING / STARTING / FAILED indicator + replica count + URL + actions
|
||||||
|
HistoryDisclosure.tsx # Past deployments table with inline log expansion
|
||||||
|
hooks/
|
||||||
|
useDeploymentPageState.ts # Form-state orchestrator (monitoring, resources, variables, sensitiveKeys)
|
||||||
|
useFormDirty.ts # Per-tab dirty computation via JSON.stringify compare
|
||||||
|
useUnsavedChangesBlocker.ts # React Router v6 useBlocker + DS AlertDialog
|
||||||
|
utils/
|
||||||
|
deriveAppName.ts # Filename → app name pure function
|
||||||
|
deriveAppName.test.ts # 9 Vitest cases
|
||||||
|
```
|
||||||
|
|
||||||
|
Touched shared files:
|
||||||
|
- `ui/src/components/StartupLogPanel.tsx` — accepts `className`, flex-grows in container (dropped fixed 300px maxHeight)
|
||||||
|
- `ui/src/api/queries/admin/apps.ts` — added `useDirtyState`, `Deployment.deployedConfigSnapshot` type
|
||||||
|
- `ui/src/api/queries/commands.ts` — `useUpdateApplicationConfig` accepts `apply?: 'staged' | 'live'`
|
||||||
|
- `ui/src/router.tsx` — routes `/apps/new` and `/apps/:appId` to `AppDeploymentPage`
|
||||||
|
- `ui/src/pages/AppsTab/AppsTab.tsx` — shrunk 1387 → 109 lines (list only)
|
||||||
|
|
||||||
|
### Docs delivered
|
||||||
|
|
||||||
|
- `.claude/rules/ui.md` — Deployments bullet rewritten for the unified page
|
||||||
|
- `.claude/rules/app-classes.md` — `ApplicationConfigController` gains `?apply` note; `AppController` gains dirty-state endpoint; `PostgresDeploymentRepository` notes the snapshot column
|
||||||
|
- `docs/superpowers/specs/2026-04-22-app-deployment-page-design.md`
|
||||||
|
- `docs/superpowers/plans/2026-04-22-app-deployment-page.md`
|
||||||
|
|
||||||
|
## Gitea issues opened this session (cameleer/cameleer-server)
|
||||||
|
|
||||||
|
### [#147 — Concurrent-edit protection on app deployment page (optimistic locking)](https://gitea.siegeln.net/cameleer/cameleer-server/issues/147)
|
||||||
|
Deferred during brainstorming. Two browser sessions editing the same app have no last-write-wins protection. Proposed fix is `If-Match` / `ETag` on config + container-config + JAR upload endpoints using `app.updated_at`. Not blocking single-operator use.
|
||||||
|
|
||||||
|
### [#148 — Persist deployment-page monitoring fields end-to-end](https://gitea.siegeln.net/cameleer/cameleer-server/issues/148)
|
||||||
|
**Important.** The Monitoring tab renders five controls that are currently **UI-only**: `payloadSize` + `payloadUnit`, `metricsInterval`, `replayEnabled`, `routeControlEnabled`. They do not persist to the agent because the fields don't exist on `com.cameleer.common.model.ApplicationConfig` and aren't part of the agent protocol. The old `CreateAppView` had the same gap — this is not a new regression, but the user has stated these must actually affect agent behavior. Fix requires cross-repo work (cameleer-common model additions + cameleer-server server wiring + cameleer agent protocol handling + agent-side gating behaviour).
|
||||||
|
|
||||||
|
## Open gaps to tackle next session
|
||||||
|
|
||||||
|
### 1. Task 13.1 — finish manual browser QA
|
||||||
|
|
||||||
|
Partial coverage so far: save/redeploy happy path, ENV pill styling, tab seam, variables view switcher, toast (all landed + verified). Still unverified:
|
||||||
|
|
||||||
|
- Checkpoint restore flow (hydrate form from past snapshot → Save → Redeploy)
|
||||||
|
- Deploy failure path (FAILED status → snapshot stays null → primary button still shows Redeploy)
|
||||||
|
- Unsaved-changes dialog on in-app navigation (sidebar click with dirty form)
|
||||||
|
- Env switch with dirty form (should discard silently)
|
||||||
|
- End-to-end deploy against real Docker daemon — see "Docker deploy setup" below
|
||||||
|
- Per-tab `*` dirty marker visibility across all 4 staged tabs
|
||||||
|
|
||||||
|
### 2. Docker deploy setup (needed to fully exercise E2E)
|
||||||
|
|
||||||
|
Current `docker-compose.yml` sets `CAMELEER_SERVER_RUNTIME_ENABLED: "false"` so `DisabledRuntimeOrchestrator` rejects deploys with `UnsupportedOperationException`. To actually test deploy end-to-end, pick one:
|
||||||
|
|
||||||
|
- **Path A (quick):** `docker compose up -d cameleer-postgres cameleer-clickhouse` only, then `mvn -pl cameleer-server-app spring-boot:run` on the host + `npm run dev` for the UI. Server uses host Docker daemon directly. Runtime enabled by default via `application.yml`.
|
||||||
|
- **Path B (compose-native):** enable runtime in compose by mounting `/var/run/docker.sock`, setting `CAMELEER_SERVER_RUNTIME_ENABLED: "true"` + `CAMELEER_SERVER_RUNTIME_DOCKERNETWORK: cameleer-traefik`, pre-creating the `cameleer-traefik` network, adding `CAMELEER_SERVER_RUNTIME_JARDOCKERVOLUME` for shared JAR storage, and adding a Traefik service for routing. This is a fully separate task — would need its own plan.
|
||||||
|
|
||||||
|
Recommend Path A for finishing QA; Path B only if you want compose to be fully deployable.
|
||||||
|
|
||||||
|
### 3. Deferred code-review items
|
||||||
|
|
||||||
|
All flagged during the final integration review. None are blockers; each is a follow-up.
|
||||||
|
|
||||||
|
- **DEGRADED deployments aren't checkpoints** — `PostgresDeploymentRepository.findLatestSuccessfulByAppAndEnv` filters `status = 'RUNNING'` but the executor writes the snapshot before the status is resolved (so a DEGRADED deployment has a snapshot). Either include `DEGRADED` in the filter, or skip snapshot on DEGRADED. Pick one; document the choice.
|
||||||
|
- **`Checkpoints.tsx` restore on null snapshot is a silent no-op** — should surface a toast like "This checkpoint predates snapshotting and cannot be restored." Currently returns early with no feedback.
|
||||||
|
- **Missing IT: FAILED deploy leaves snapshot NULL** — `DeploymentSnapshotIT` tests the success case and general "snapshot appears on RUNNING" but doesn't explicitly lock in the FAILED → null guarantee. Add a one-line assertion.
|
||||||
|
- **`HistoryDisclosure` expanded log doesn't `scrollIntoView`** — on long histories the startup-log panel opens off-screen. Minor UX rough edge.
|
||||||
|
- **OpenAPI `@Parameter` missing on `apply` query param** — not critical, just improves generated Swagger docs. Add `@Parameter(name = "apply", description = "staged | live (default: live)")` to `ApplicationConfigController.updateConfig`.
|
||||||
|
|
||||||
|
### 4. Minor tech debt introduced this session
|
||||||
|
|
||||||
|
- `samplingRate` normalization hack in `useDeploymentPageState.ts`: `Number.isInteger(x) ? \`${x}.0\` : String(x)` — works around `1.0` parsing back as `1`, but breaks for values like `1.10` (round-trips to `1.1`). A cleaner fix is to compare as numbers, not strings, in `useFormDirty`.
|
||||||
|
- `useDirtyState` defaults to `?? true` during loading (so the button defaults to `Redeploy`, the fail-safe choice). Spurious Redeploy clicks are harmless, but the "Save (disabled)" UX would be more correct during initial load. Consider a loading-aware ternary if it becomes user-visible.
|
||||||
|
- `ApplicationConfigController.updateConfig` returns `ResponseEntity.status(400).build()` (empty body) on unknown `apply` values. Consider a structured error body consistent with other 400s in the codebase.
|
||||||
|
- GitNexus index stats (`AGENTS.md`, `CLAUDE.md`) refreshed several times during the session — these are auto-generated and will refresh again on next `npx gitnexus analyze`.
|
||||||
|
|
||||||
|
### 5. Behavioural caveats to know about
|
||||||
|
|
||||||
|
- **Agent config writes from the Dashboard / Runtime pages** still use `useUpdateApplicationConfig` with default `apply='live'` — they push SSE immediately as before. Only Deployment-page writes use `apply=staged`. This is by design.
|
||||||
|
- **Traces & Taps + Route Recording tabs** on the Deployment page write with `apply='live'` (immediate SSE). They do **not** participate in dirty detection. The LiveBanner explains this to the user.
|
||||||
|
- **Slug is immutable** — enforced both server-side (regex + Jackson drops unknown fields on PUT) and client-side (IdentitySection renders slug as `MonoText`, never `Input`).
|
||||||
|
- **Environment is immutable after create** — the deployment page has no env selector; the environment chip is read-only and colored via `envColorVar` per the env's configured color.
|
||||||
|
- **Dirty detection ignores `version`, `updatedAt`, `updatedBy`, `environment`, `application`** on agent config — these get bumped server-side on every save and would otherwise spuriously mark the page dirty. Scrubbing happens in `DirtyStateCalculator.scrubAgentConfig`.
|
||||||
|
|
||||||
|
## Recommended next-session kickoff
|
||||||
|
|
||||||
|
1. Run `docker compose up -d cameleer-postgres cameleer-clickhouse`, then `mvn -pl cameleer-server-app spring-boot:run` and `npm run dev` in two terminals.
|
||||||
|
2. Walk through the rest of Task 13.1 (checkpoint restore, deploy failure, unsaved dialog, env switch).
|
||||||
|
3. File any new bugs found. Address the deferred review items (section 3) in small PR-sized commits.
|
||||||
|
4. Decide which of #148's cross-repo work to tackle — cleanest path is: (a) extend `ApplicationConfig` in cameleer-common, (b) wire server side, (c) coordinate agent-side behaviour gating.
|
||||||
|
5. If you want compose-native deploy, open a separate ticket or spec for Path B from "Docker deploy setup" above.
|
||||||
|
|
||||||
|
## Commit range summary
|
||||||
|
|
||||||
|
```
|
||||||
|
1a376eb2..0a71bca7 (43 commits)
|
||||||
|
ff951877 db(deploy): add deployments.deployed_config_snapshot column (V3)
|
||||||
|
d580b6e9 core(deploy): add DeploymentConfigSnapshot record
|
||||||
|
06fa7d83 core(deploy): type jarVersionId as UUID (match domain convention)
|
||||||
|
7f9cfc7f core(deploy): add deployedConfigSnapshot field to Deployment model
|
||||||
|
d3e86b9d storage(deploy): persist deployed_config_snapshot as JSONB
|
||||||
|
9b851c46 test(deploy): autowire repository in snapshot IT (JavaTimeModule-safe)
|
||||||
|
a79eafea runtime(deploy): capture config snapshot on RUNNING transition
|
||||||
|
9b124027 test(deploy): assert containerConfig round-trip + strict RUNNING in snapshot IT
|
||||||
|
76129d40 api(config): ?apply=staged|live gates SSE push on PUT /apps/{slug}/config
|
||||||
|
e716dbf8 test(config): verify audit action in staged/live config IT
|
||||||
|
76352c0d test(config): tighten audit assertions + @DirtiesContext on ApplicationConfigControllerIT
|
||||||
|
e4ccce1e core(deploy): add DirtyStateCalculator + DirtyStateResult
|
||||||
|
24464c07 core(deploy): recurse into nested diffs + unquote scalar values in DirtyStateCalculator
|
||||||
|
6591f2fd api(apps): GET /apps/{slug}/dirty-state returns desired-vs-deployed diff
|
||||||
|
97f25b4c test(deploy): register JavaTimeModule in DirtyStateCalculator unit test
|
||||||
|
0434299d api(schema): regenerate OpenAPI + schema.d.ts for deployment page
|
||||||
|
60529757 ui(deploy): scaffold AppDeploymentPage + route /apps/new and /apps/:slug
|
||||||
|
52ff385b ui(api): add useDirtyState + apply=staged|live on useUpdateApplicationConfig
|
||||||
|
d067490f ui(deploy): add deriveAppName pure function + tests
|
||||||
|
00c7c0cd ui(deploy): Identity & Artifact section with filename auto-derive
|
||||||
|
08efdfa9 ui(deploy): Checkpoints disclosure (hides current deployment, flags pruned JARs)
|
||||||
|
cc193a10 ui(deploy): add useDeploymentPageState orchestrator hook
|
||||||
|
4f5a11f7 ui(deploy): extract MonitoringTab component
|
||||||
|
5c48b780 ui(deploy): extract ResourcesTab component
|
||||||
|
bb06c4c6 ui(deploy): extract VariablesTab component
|
||||||
|
f487e6ca ui(deploy): extract SensitiveKeysTab component
|
||||||
|
b7c0a225 ui(deploy): LiveBanner component for live-apply tabs
|
||||||
|
e96c3cd0 ui(deploy): Traces & Taps + Route Recording tabs with live banner
|
||||||
|
98a7b781 ui(deploy): StatusCard for Deployment tab
|
||||||
|
063a4a55 ui(deploy): HistoryDisclosure with inline log expansion
|
||||||
|
1579f10a ui(deploy): DeploymentTab + flex-grow StartupLogPanel
|
||||||
|
42fb6c8b ui(deploy): useFormDirty hook for per-tab dirty markers
|
||||||
|
0e4166bd ui(deploy): PrimaryActionButton + computeMode state-machine helper
|
||||||
|
b1bdb88e ui(deploy): compose page — save/redeploy/checkpoints wired end-to-end
|
||||||
|
3a649f40 ui(deploy): router blocker + DS dialog for unsaved edits
|
||||||
|
5a7c0ce4 ui(deploy): delete CreateAppView + AppDetailView + ConfigSubTab
|
||||||
|
d5957468 docs(rules): update ui.md Deployments bullet for unified deployment page
|
||||||
|
6d5ce606 docs(rules): document ?apply flag + snapshot column in app-classes
|
||||||
|
d33c039a fix(deploy): address final review — sensitiveKeys snapshot, dirty scrubbing, transition race, refetch invalidations
|
||||||
|
b7b6bd2a ui(deploy): port missing agent-config fields, var-view switcher, env pill, tab seam
|
||||||
|
0a71bca7 fix(deploy): redeploy button after save, disable save when clean, success toast
|
||||||
|
```
|
||||||
|
|
||||||
|
Plus this handoff commit + the GitNexus index-stats refresh.
|
||||||
Reference in New Issue
Block a user