chore(agent): track .agent/ planning tree in git
Remove the blanket .agent/ gitignore so the phase roadmap, user stories, reports, and PLAN.md are versioned with the code. Only runtime artifacts (.agent/phase-sessions/, .agent/pipeline.log) remain ignored. Update AGENTS.md git protocol rule to match.
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
# Phase 25 — Background: No Motion, Only Fading Light
|
||||
|
||||
**Source:** Owner report (2026-08-25, chat): the background "jitters down
|
||||
and to the right every second and it slowly blinks brighter and darker. It
|
||||
should be smooth, fluxuating, dimming and brightening, but not moving.
|
||||
Different bright spots should slowly fade in and out."
|
||||
**Story:** `.agent/user_stories/background-no-motion.md` (created by task 02)
|
||||
**Context:** `frontend/assets/styles.css` — the background block:
|
||||
`body::before` (44px grid, 1px lines at 60% `--line` alpha, widened radial
|
||||
mask, `animation: bg-grid-drift 60s linear infinite` → `0 0` →
|
||||
`44px 44px` ≈ 0.73px/s down-right) and `body::after` (indigo + cyan radial
|
||||
glows, `animation: bg-glow-breathe 14s ease-in-out infinite alternate` →
|
||||
opacity 0.85↔1 + scale 1↔1.05). Both are
|
||||
`position: fixed; inset: 0; z-index: -1; pointer-events: none`; `<html>`
|
||||
owns the `var(--bg)` canvas, `<body>` stays transparent (no-occlusion
|
||||
contract). Pins to adapt: `tests/unit/test_background_animation.py`,
|
||||
`tests/e2e/test_background_animation.py`,
|
||||
`tests/e2e/test_dark_tech_theme.py` (`test_animated_background` pins
|
||||
60s/14s; `test_reduced_motion_honored` pins the two body pseudo-layers).
|
||||
|
||||
## Objective
|
||||
Stop the background from moving entirely, and replace the uniform
|
||||
whole-layer "blink" with **different bright spots that slowly fade in and
|
||||
out**: the grid becomes a static texture, and three soft glow spots
|
||||
(phase-08 colors/positions, plus a third spot) each run their own
|
||||
slow, **opacity-only** fade cycle at a different period, so the
|
||||
background's brightness fluxuates smoothly and irregularly — no blink, no
|
||||
jitter, no motion.
|
||||
|
||||
## Root cause (found from the code, 2026-08-25)
|
||||
1. **"Jitters down and to the right every second"** = `bg-grid-drift`:
|
||||
44px/60s (≈0.73px/s) in the diagonal `44px 44px` direction (exactly
|
||||
down-right). A 1px grid line translated sub-pixel-by-sub-pixel is
|
||||
rasterized with per-frame stepping/shimmer — perceived as a once-per-
|
||||
second jitter, not smooth drift. Phase 22 made the drift *visible*;
|
||||
that is precisely why it now reads as jitter.
|
||||
2. **"Slowly blinks brighter and darker"** = `bg-glow-breathe`: a uniform
|
||||
whole-layer opacity swing 0.85↔1 over 14s (alternate) plus
|
||||
`scale(1)↔scale(1.05)` — the entire background pulses in unison (the
|
||||
scale adds a faint zoom). One synchronized pulse reads as a blink; the
|
||||
owner wants independent spots instead.
|
||||
|
||||
Phase 22 served the phase-08 design intent (grid drift + whole-layer
|
||||
breathe). The owner now supersedes that design intent — this is an
|
||||
owner revision of a **design comment**, not of any LOCKED anchor: A1–A17
|
||||
are untouched, and the phase-08 anchors this phase must still honor are
|
||||
pure CSS / zero JS / no CDN / no new assets / no `filter: blur` (A11 +
|
||||
the block's perf note).
|
||||
|
||||
## Owner direction (2026-08-25, verbatim)
|
||||
> "It should be smooth, fluxuating, dimming and brightening, but not
|
||||
> moving. Different bright spots should slowly fade in and out."
|
||||
|
||||
1. **No movement** — no grid drift, no `scale`/`transform`, no
|
||||
`background-position` animation, anywhere in the background.
|
||||
2. **Fluxuating brightness** — overall page brightness varies smoothly and
|
||||
irregularly (not one synchronized pulse).
|
||||
3. **Different bright spots** — multiple glow spots, each fading in and
|
||||
out on its own slow cycle.
|
||||
4. **The static grid stays** — the owner rejected the grid's *motion*, not
|
||||
the grid; it remains as a still texture. (If the owner later wants the
|
||||
grid gone, that is a follow-up, not this phase.)
|
||||
|
||||
## Design (pure CSS, zero JS — A11 anchor)
|
||||
- `body::before` — grid: **remove** the `animation`; **delete**
|
||||
`@keyframes bg-grid-drift`. Keep the 44px cells, 60% `--line` alpha
|
||||
lines, and widened radial mask (the static texture).
|
||||
- Three glow-spot layers, one soft radial gradient each, **opacity-only**
|
||||
keyframes (`0%,100%` low → `50%` 1, `ease-in-out`, `infinite`), with
|
||||
different durations + negative delays so the cycles are out of phase
|
||||
(periods 26/34/42s → LCM 4641s, the composite pattern effectively never
|
||||
repeats within a viewing session):
|
||||
|
||||
| layer | spot (gradient) | keyframes | cycle |
|
||||
|---|---|---|---|
|
||||
| `body::after` | indigo `rgb(109 120 242 / 0.14)`, circle 56rem at 12% 8% (phase-08) | `bg-glow-a` | 26s, low opacity 0.25 |
|
||||
| `html::before` | cyan `rgb(34 211 238 / 0.10)`, circle 60rem at 88% 92% (phase-08) | `bg-glow-b` | 34s, delay −12s, low 0.20 |
|
||||
| `html::after` | indigo `rgb(109 120 242 / 0.09)`, circle 52rem at 14% 86% | `bg-glow-c` | 42s, delay −23s, low 0.15 |
|
||||
|
||||
- `html::before` / `html::after` join `body::after` as background layers:
|
||||
`<html>` is the root stacking context — its `z-index: -1`
|
||||
pseudo-elements paint **above** the `var(--bg)` canvas and **below**
|
||||
the transparent, non-stacking `<body>`'s content, so the no-occlusion
|
||||
contract holds unchanged (verify in the E2E, not just assume).
|
||||
- All four layers keep: `content: ""; position: fixed; inset: 0;
|
||||
z-index: -1; pointer-events: none;`
|
||||
- `prefers-reduced-motion: reduce` stills **all four** layers
|
||||
(`animation: none`).
|
||||
- No `filter` (phase-08 no-blur perf anchor), no JS, no new assets; the
|
||||
WCAG palette and every text contrast pair are untouched (the layers
|
||||
carry no text). Opacity-only keyframes stay compositor-friendly.
|
||||
|
||||
## Dependencies
|
||||
- `08_story_dark_tech_theme` (complete) — the layers, the palette, the
|
||||
pure-CSS / no-blur / no-CDN anchors, and the 60s/14s pins in
|
||||
`tests/e2e/test_dark_tech_theme.py` this phase adapts.
|
||||
- `22_background_animation` (complete) — the current implementation and
|
||||
the unit/E2E pins this phase supersedes.
|
||||
- `07_story_responsive_polish` (complete) — the 360px overflow pin (the
|
||||
layers are `fixed; inset: 0` — they must add no width).
|
||||
|
||||
## Tasks
|
||||
1. `01_still_background_css.md` — the CSS redesign (static grid + three
|
||||
opacity-only glow fades) + unit source pins (new
|
||||
`tests/unit/test_background_no_motion.py`; the phase-22 pins in
|
||||
`tests/unit/test_background_animation.py` adapted).
|
||||
2. `02_e2e_story_suite_commit.md` — `tests/e2e/test_background_no_motion.py`
|
||||
(the story gate, isolated), the regression suites adapted
|
||||
(`test_background_animation.py`, `test_dark_tech_theme.py`), the story
|
||||
file, the phase report + screenshots, final validation, the single
|
||||
atomic commit, phase move to `complete/`.
|
||||
|
||||
## Locked decisions
|
||||
- **A11 untouched** — vanilla HTML/CSS/JS in git, no CDN, zero JS, system
|
||||
fonts; the whole change is CSS. **No `filter`/`blur`** (phase-08 perf
|
||||
anchor). No new assets. **No anchor changed** — the superseded spec is
|
||||
the phase-08 *design intent* (grid drift + whole-layer breathe), not a
|
||||
LOCKED decision; the owner's 2026-08-25 direction is recorded above as
|
||||
the revision. **A16 honored** — one new story E2E suite + adapted
|
||||
regressions. **A17 honored** — one atomic `--no-gpg-sign` commit.
|
||||
|
||||
## Testing & Quality
|
||||
- **Unit (source-level, repo source-pin pattern):**
|
||||
- New `tests/unit/test_background_no_motion.py` — pins the full new
|
||||
contract (task 01, step 6).
|
||||
- Adapted `tests/unit/test_background_animation.py` — the phase-22
|
||||
drift/breathe pins flip to the new contract; the generic
|
||||
layer-plumbing and no-blur/no-JS sections stay (task 01, step 7).
|
||||
- **Integration:** none (no `app/` changes).
|
||||
- **Coverage:** frontend-only; the >90% `app/` gate is unaffected (TOTAL
|
||||
must stay ≥ the pre-change number).
|
||||
- **E2E:** `tests/e2e/test_background_no_motion.py` (task 02), green
|
||||
**in isolation** (prereq `podman compose up -d db`); regressions green
|
||||
in isolation after adaptation: `test_background_animation.py`,
|
||||
`test_dark_tech_theme.py`, `test_responsive_polish.py`.
|
||||
- **Lint/types:** `uv run ruff check . && uv run pyright` clean.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] No movement: the grid is static (`bg-grid-drift` gone, no
|
||||
`animation` on `body::before`) and **no `bg-*` keyframe animates
|
||||
anything but `opacity`** — audited in a real Chromium via
|
||||
`document.styleSheets` (E2E test 3).
|
||||
- [ ] Three distinct bright spots (`body::after`, `html::before`,
|
||||
`html::after`) run distinct slow opacity fades (26s/34s/42s,
|
||||
out of phase); the timelines advance; the layer opacity and a
|
||||
clipped screenshot of the glow region measurably change within a
|
||||
few seconds (E2E tests 2, 4, 5).
|
||||
- [ ] Contracts hold: all four layers `fixed; inset: 0; z-index: -1;
|
||||
pointer-events: none`; `<html>` keeps the `var(--bg)` canvas and
|
||||
`<body>` stays transparent (no occlusion); `prefers-reduced-motion`
|
||||
stills all four; no horizontal overflow at 360px (E2E tests 6–8).
|
||||
- [ ] `.agent/reports/25_background_no_motion/` documents the two root
|
||||
causes with before/after screenshot pairs
|
||||
(`.agent/screenshots/25_background_no_motion/`).
|
||||
- [ ] `uv run pytest` green; `uv run pytest --cov=app
|
||||
--cov-report=term-missing` TOTAL ≥ pre-change number (gate >90%).
|
||||
- [ ] `uv run pytest tests/e2e/test_background_no_motion.py -v --no-cov`
|
||||
green in isolation; `test_background_animation.py`,
|
||||
`test_dark_tech_theme.py`, `test_responsive_polish.py` green in
|
||||
isolation after adaptation.
|
||||
- [ ] `uv run ruff check . && uv run pyright` clean.
|
||||
- [ ] UI Structure Check (AGENTS.md rule 5): layers stay behind content,
|
||||
no text/contrast impact, no 360px overflow.
|
||||
- [ ] `.agent/user_stories/background-no-motion.md` exists; the old
|
||||
`background-animation.md` story carries a supersession note.
|
||||
- [ ] One `--no-gpg-sign` commit staging **only this phase's files** (the
|
||||
unrelated dirty `TODO.md` must NOT be staged);
|
||||
`.agent/phases/todo/25_background_no_motion/` moved to
|
||||
`.agent/phases/complete/`.
|
||||
|
||||
## Commit
|
||||
```bash
|
||||
git add -f .agent/phases/complete/25_background_no_motion \
|
||||
.agent/user_stories/background-no-motion.md \
|
||||
.agent/user_stories/background-animation.md \
|
||||
.agent/reports/25_background_no_motion \
|
||||
.agent/screenshots/25_background_no_motion
|
||||
git add frontend/assets/styles.css tests/unit tests/e2e
|
||||
git commit --no-gpg-sign -m "fix(ui): background no longer moves — static grid, three glow spots fading in and out on their own slow cycles (owner 2026-08-25)"
|
||||
```
|
||||
@@ -0,0 +1,149 @@
|
||||
# Task 02 — Story E2E Suite, Regression Adaptations, Story File, Commit
|
||||
|
||||
**Phase:** `25_background_no_motion` · **Story:** `.agent/user_stories/background-no-motion.md`
|
||||
|
||||
## Objective
|
||||
Prove the new behavior in a real Chromium viewport (no movement,
|
||||
independent slow fades, all layer contracts), adapt the two
|
||||
phase-08/22 E2E suites to the new contract, write the story file and the
|
||||
phase report, run the final validation, and land the single atomic
|
||||
commit.
|
||||
|
||||
## Work
|
||||
1. **New `tests/e2e/test_background_no_motion.py`** — the story gate,
|
||||
run in isolation (fixtures as in `tests/e2e/test_background_animation.py`:
|
||||
`page`, `app_url`, `db_ready`; keep that file's Chromium notes:
|
||||
pseudo-element CSS animations are enumerated by
|
||||
`document.getAnimations()`, and the html pseudo-layers' computed
|
||||
styles come from
|
||||
`getComputedStyle(document.documentElement, "::before"/"::after")`).
|
||||
Tests (8):
|
||||
1. `test_grid_layer_is_static` — computed `animationName` of
|
||||
`body::before` is `"none"`; no `bg-grid-drift` entry in
|
||||
`document.getAnimations()`; the grid `backgroundImage` is still
|
||||
present (the static texture survives).
|
||||
2. `test_three_glow_layers_run_distinct_fades` — `body::after` →
|
||||
`bg-glow-a` (26s), `documentElement::before` → `bg-glow-b` (34s),
|
||||
`documentElement::after` → `bg-glow-c` (42s); each
|
||||
`ease-in-out` + `infinite`, with a matching
|
||||
`playState === "running"` entry in the document animation list;
|
||||
the three durations are pairwise distinct.
|
||||
3. `test_no_motion_properties_in_background_keyframes` — walk
|
||||
`document.styleSheets`; for every `CSSRule.KEYFRAMES_RULE` whose
|
||||
name starts with `bg-`, collect the declared property names of
|
||||
every keyframe frame (iterate `frame.style`); the set across all
|
||||
frames is exactly `{"opacity"}` — the deterministic no-movement
|
||||
proof (no `transform`/`background-position` anywhere).
|
||||
4. `test_glow_timelines_advance` — poll until all three timelines
|
||||
report `currentTime > 0` (the phase-22 pattern: headless Chromium
|
||||
starts the document timeline ~1s after load), sample all three,
|
||||
wait ~500ms, each advanced ≥ 200ms.
|
||||
5. `test_background_light_actually_changes` — (a) read the computed
|
||||
`opacity` of `body::after` (or `documentElement::before`) at t0 and
|
||||
poll up to ~8s until |Δ| ≥ 0.05 (a real fade, not a frozen frame);
|
||||
(b) take two clipped screenshots ~4s apart of the bottom-left glow
|
||||
region (the `html::after` spot at 14%/86%, e.g. clip
|
||||
`{"x": 0, "y": 500, "width": 500, "height": 300}`) and assert the
|
||||
bytes differ — the light visibly changes while nothing moves
|
||||
(a fresh `/` page has no other animation, so the diff is the
|
||||
background's).
|
||||
6. `test_background_layers_contracts` — all four pseudo-elements
|
||||
(`body::before`, `body::after`, `documentElement::before`,
|
||||
`documentElement::after`): `position: fixed`, `z-index: -1`,
|
||||
`pointer-events: none`, top/right/bottom/left all `0px`;
|
||||
`documentElement` computed background is `rgb(10, 14, 23)`
|
||||
(`var(--bg)` — the canvas stays on `html`); `document.body`
|
||||
computed background is `rgba(0, 0, 0, 0)` (no occlusion).
|
||||
7. `test_reduced_motion_stills_all_layers` — `reduced_motion="reduce"`
|
||||
context: all four pseudo-layers report computed `animationName`
|
||||
`"none"` and still carry a `backgroundImage` (static background
|
||||
remains visible).
|
||||
8. `test_no_horizontal_overflow_with_layers` — 360px viewport:
|
||||
`documentElement.scrollWidth <= clientWidth` (the phase-07 pin).
|
||||
2. **Adapt `tests/e2e/test_background_animation.py`** (the phase-22 story
|
||||
suite — now a regression): replace `test_grid_layer_animation_running`,
|
||||
`test_glow_layer_animation_running`, `test_animations_advance` with the
|
||||
new-contract equivalents (grid static; `body::after` runs
|
||||
`bg-glow-a` running; the glow timelines advance — delegate to the
|
||||
same JS-report pattern); keep and extend
|
||||
`test_background_layers_contracts` to the two new html pseudo-layers;
|
||||
keep `test_no_horizontal_overflow_with_layers`; rewrite the module
|
||||
docstring (phase 25 supersedes the phase-22 pins; pointer to
|
||||
`background-no-motion.md`).
|
||||
3. **Adapt `tests/e2e/test_dark_tech_theme.py`:**
|
||||
- `test_animated_background` — the 60s/14s recipe is gone; new
|
||||
contract: `body::before` `animationName == "none"` with its grid
|
||||
image present (static texture); the three glow layers run
|
||||
`bg-glow-a/b/c` at 26s/34s/42s (read the html pseudo-layers off
|
||||
`documentElement`); update the docstring/AC text accordingly.
|
||||
- `test_reduced_motion_honored` — assert **all four** pseudo-layers
|
||||
(body `::before`/`::after` + documentElement `::before`/`::after`)
|
||||
report `animationName` `"none"` and keep their images.
|
||||
4. **`.agent/user_stories/background-no-motion.md`** — the story file, in
|
||||
the repo's story format (model it on `background-animation.md`):
|
||||
verbatim owner report (2026-08-25); Given/When/Then narrative; root
|
||||
causes (sub-pixel 0.73px/s grid drift = once-per-second down-right
|
||||
jitter; whole-layer 0.85↔1 + scale breathe = uniform blink); the fix
|
||||
table (phase-22 → phase-25: grid static; three spots, opacity-only,
|
||||
26/34/42s, delays 0/−12s/−23s, lows 0.25/0.20/0.15); acceptance
|
||||
criteria = the 8 E2E tests + unit pins + regressions; UI
|
||||
Visualization & Structure (the four layers, stacking/no-occlusion,
|
||||
opacity-only motion, reduced-motion); Playwright Mapping Rule
|
||||
(test → `tests/e2e/test_background_no_motion.py`).
|
||||
5. **`.agent/user_stories/background-animation.md`** — add a short
|
||||
supersession note at the top (the motion design is superseded by the
|
||||
owner direction 2026-08-25 — see `background-no-motion.md`); do not
|
||||
rewrite the phase-22 history.
|
||||
6. **`.agent/reports/25_background_no_motion/`** — short report: the two
|
||||
root causes (with the phase-22 measurements as context), the design
|
||||
change, and the screenshot pairs: the task-01 `before.png`/
|
||||
`before_4s.png` (jitter + uniform pulse) plus a new `after.png`/
|
||||
`after_4s.png` pair captured the same way (the after pair must show a
|
||||
brightness change with no positional shift of the grid).
|
||||
7. **Final validation** (in order; DB up: `podman compose up -d db`):
|
||||
- `uv run pytest` (unit + integration)
|
||||
- `uv run pytest --cov=app --cov-report=term-missing` — TOTAL ≥
|
||||
pre-change, app/ gate >90%
|
||||
- `uv run pytest tests/e2e/test_background_no_motion.py -v --no-cov`
|
||||
(the story gate, **in isolation**)
|
||||
- `uv run pytest tests/e2e/test_background_animation.py -v --no-cov`
|
||||
(in isolation, adapted)
|
||||
- `uv run pytest tests/e2e/test_dark_tech_theme.py -v --no-cov` and
|
||||
`uv run pytest tests/e2e/test_responsive_polish.py -v --no-cov`
|
||||
(in isolation, regressions)
|
||||
- `uv run ruff check . && uv run pyright`
|
||||
8. **One atomic commit** — stage **only this phase's files**; the
|
||||
unrelated dirty `TODO.md` in the worktree must NOT be staged:
|
||||
```bash
|
||||
mv .agent/phases/todo/25_background_no_motion .agent/phases/complete/25_background_no_motion
|
||||
git add -f .agent/phases/complete/25_background_no_motion \
|
||||
.agent/user_stories/background-no-motion.md \
|
||||
.agent/user_stories/background-animation.md \
|
||||
.agent/reports/25_background_no_motion \
|
||||
.agent/screenshots/25_background_no_motion
|
||||
git add frontend/assets/styles.css tests/unit tests/e2e
|
||||
git commit --no-gpg-sign -m "fix(ui): background no longer moves — static grid, three glow spots fading in and out on their own slow cycles (owner 2026-08-25)"
|
||||
```
|
||||
(Move the directory first so the committed copy lives in `complete/`;
|
||||
`.agent/` is gitignored by design, hence `git add -f`.)
|
||||
|
||||
## Testing & Quality
|
||||
- The new E2E suite is the story gate (A16): green **in isolation**;
|
||||
the adapted regression suites green in isolation; `test_responsive_
|
||||
polish.py` green in isolation.
|
||||
- `app/` coverage >90% and TOTAL ≥ pre-change (`app/` is untouched).
|
||||
- ruff + pyright clean.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] All 8 tests in `tests/e2e/test_background_no_motion.py` pass in
|
||||
isolation.
|
||||
- [ ] `test_background_animation.py` and `test_dark_tech_theme.py`
|
||||
adapted and green in isolation; `test_responsive_polish.py` green
|
||||
in isolation.
|
||||
- [ ] `uv run pytest` green; `uv run pytest --cov=app` TOTAL ≥
|
||||
pre-change; `uv run ruff check . && uv run pyright` clean.
|
||||
- [ ] Story file exists; the old story carries the supersession note;
|
||||
the report + four screenshots exist.
|
||||
- [ ] Exactly one `--no-gpg-sign` commit, staging only this phase's
|
||||
files (`git status` shows no staged `TODO.md`); the phase
|
||||
directory now lives in `.agent/phases/complete/`.
|
||||
Reference in New Issue
Block a user