feat: Added hidden boolean column to Creator model, migration marking T…
- "backend/models.py" - "backend/routers/creators.py" - "alembic/versions/009_add_creator_hidden_flag.py" GSD-Task: S02/T01
This commit is contained in:
parent
6d4390414a
commit
127919565a
12 changed files with 526 additions and 4 deletions
|
|
@ -6,6 +6,6 @@ Fix the broken and embarrassing things first. Key moment links that 404, test da
|
|||
## Slice Overview
|
||||
| ID | Slice | Risk | Depends | Done | After this |
|
||||
|----|-------|------|---------|------|------------|
|
||||
| S01 | Fix Key Moment Search Links | high | — | ⬜ | Search 'compression', click any key moment result → lands on parent technique page with key moment visible |
|
||||
| S01 | Fix Key Moment Search Links | high | — | ✅ | Search 'compression', click any key moment result → lands on parent technique page with key moment visible |
|
||||
| S02 | Trust & Credibility Cleanup | low | — | ⬜ | Creators page has no TestCreator. Footer shows clean version info. Search results have no yellow jargon banner. |
|
||||
| S03 | Homepage Cards & Creator Metric Polish | low | — | ⬜ | Homepage technique cards show sub-topic tags and key moment count. Creator pages show technique count by topic instead of '0 views'. |
|
||||
|
|
|
|||
94
.gsd/milestones/M008/slices/S01/S01-SUMMARY.md
Normal file
94
.gsd/milestones/M008/slices/S01/S01-SUMMARY.md
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
---
|
||||
id: S01
|
||||
parent: M008
|
||||
milestone: M008
|
||||
provides:
|
||||
- technique_page_slug field on all SearchResultItem responses
|
||||
- Hash-scroll anchors (km-{id}) on key moment list items in TechniquePage
|
||||
requires:
|
||||
[]
|
||||
affects:
|
||||
[]
|
||||
key_files:
|
||||
- backend/schemas.py
|
||||
- backend/search_service.py
|
||||
- backend/pipeline/stages.py
|
||||
- backend/pipeline/qdrant_client.py
|
||||
- backend/tests/test_search.py
|
||||
- frontend/src/api/public-client.ts
|
||||
- frontend/src/pages/SearchResults.tsx
|
||||
- frontend/src/pages/TechniquePage.tsx
|
||||
key_decisions:
|
||||
- technique_page_slug read from Qdrant payload for key moments, from slug for technique pages, with title-based fallback for old data
|
||||
- outerjoin to TechniquePage in keyword search handles NULL technique_page_id gracefully
|
||||
- Key moment hash anchor uses km.id (UUID) for uniqueness
|
||||
- Fallback for missing technique_page_slug re-searches by title instead of 404ing
|
||||
patterns_established:
|
||||
- Cross-entity link resolution: when search results reference a parent entity, resolve the parent slug at query time (DB join for keyword, payload enrichment for semantic) rather than expecting the frontend to make a second API call
|
||||
- Hash-scroll pattern: anchor IDs on target elements + useEffect that fires after data load to scrollIntoView
|
||||
observability_surfaces:
|
||||
- none
|
||||
drill_down_paths:
|
||||
- .gsd/milestones/M008/slices/S01/tasks/T01-SUMMARY.md
|
||||
- .gsd/milestones/M008/slices/S01/tasks/T02-SUMMARY.md
|
||||
duration: ""
|
||||
verification_result: passed
|
||||
completed_at: 2026-03-31T05:05:15.454Z
|
||||
blocker_discovered: false
|
||||
---
|
||||
|
||||
# S01: Fix Key Moment Search Links
|
||||
|
||||
**Key moment search results now link to their parent technique page and scroll to the specific moment, instead of 404ing.**
|
||||
|
||||
## What Happened
|
||||
|
||||
Key moment search results were broken — clicking one produced a 404 because the frontend tried to navigate to `/techniques/{moment_slug}` but moments don't have their own pages. The fix required coordinated backend and frontend changes across two tasks.
|
||||
|
||||
**T01 (Backend):** Added `technique_page_slug` to the search result data flow. Four areas changed: (1) `SearchResultItem` schema gained a `technique_page_slug` field. (2) Qdrant payload enrichment in stage 6 now includes `slug` on technique page dicts and `technique_page_slug`/`technique_page_id` on key moment dicts via a `page_id_to_slug` mapping. (3) The search service's `_enrich_results()` reads `technique_page_slug` from Qdrant payloads; `keyword_search()` uses an outerjoin to TechniquePage to resolve the parent slug for key moments. (4) Three new integration tests verify correct slug population for technique pages, key moments with parents, and orphan moments.
|
||||
|
||||
**T02 (Frontend):** Three changes: (1) `SearchResultCard` routing now sends key moment clicks to `/techniques/{parent_slug}#km-{id}` (with a re-search fallback if no parent slug exists). (2) Key moment `<li>` elements in `TechniquePage` gained `id={`km-${km.id}`}` anchor attributes. (3) A `useEffect` scrolls to the hash target after technique data loads, using `scrollIntoView({ behavior: 'smooth', block: 'start' })`.
|
||||
|
||||
## Verification
|
||||
|
||||
Backend: All 8 tests pass (5 existing + 3 new) via `python -m pytest tests/test_search.py -v` inside chrysopedia-api container on ub01 (2.88s). Frontend: `npm run build` passes with zero TypeScript errors, producing a clean production bundle (735ms).
|
||||
|
||||
## Requirements Advanced
|
||||
|
||||
- R005 — Key moment search results no longer 404 — they link to the parent technique page with hash-scroll to the specific moment
|
||||
- R015 — Eliminating the 404 dead-end on key moment clicks removes a significant time-waster from the search-to-read flow
|
||||
|
||||
## Requirements Validated
|
||||
|
||||
None.
|
||||
|
||||
## New Requirements Surfaced
|
||||
|
||||
None.
|
||||
|
||||
## Requirements Invalidated or Re-scoped
|
||||
|
||||
None.
|
||||
|
||||
## Deviations
|
||||
|
||||
Fixed pre-existing bug in test seed data (ProcessingStatus.extracted → ProcessingStatus.complete). Backend tests run via SSH into container on ub01 since DB port is localhost-only.
|
||||
|
||||
## Known Limitations
|
||||
|
||||
Existing Qdrant data won't have slug fields until re-indexed. The title-based slug fallback in _enrich_results covers old data for semantic search, but keyword search always has the correct slug from the DB join. QdrantManager still uses random UUIDs for point IDs (pre-existing issue — re-indexing creates duplicates).
|
||||
|
||||
## Follow-ups
|
||||
|
||||
Re-index Qdrant after deployment so all points carry slug fields (eliminates fallback path). Address QdrantManager random UUID issue before next bulk re-index to avoid point duplication.
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `backend/schemas.py` — Added technique_page_slug: str = '' to SearchResultItem
|
||||
- `backend/search_service.py` — Added technique_page_slug population in _enrich_results (semantic) and keyword_search (DB join)
|
||||
- `backend/pipeline/stages.py` — Stage 6 now includes slug in technique page Qdrant dicts, technique_page_slug/technique_page_id in key moment dicts
|
||||
- `backend/pipeline/qdrant_client.py` — Updated payload structure documentation (no functional change — stages.py builds the dicts)
|
||||
- `backend/tests/test_search.py` — 3 new keyword search tests for technique_page_slug; fixed ProcessingStatus seed data bug
|
||||
- `frontend/src/api/public-client.ts` — Added technique_page_slug to SearchResultItem interface
|
||||
- `frontend/src/pages/SearchResults.tsx` — Key moment links now route to /techniques/{parent_slug}#km-{id} with re-search fallback
|
||||
- `frontend/src/pages/TechniquePage.tsx` — Added km-{id} anchor IDs to key moment list items; added useEffect for hash-scroll on load
|
||||
48
.gsd/milestones/M008/slices/S01/S01-UAT.md
Normal file
48
.gsd/milestones/M008/slices/S01/S01-UAT.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# S01: Fix Key Moment Search Links — UAT
|
||||
|
||||
**Milestone:** M008
|
||||
**Written:** 2026-03-31T05:05:15.454Z
|
||||
|
||||
## UAT: Fix Key Moment Search Links
|
||||
|
||||
### Preconditions
|
||||
- Chrysopedia stack running on ub01 (docker ps shows all containers healthy)
|
||||
- At least one technique page with key moments exists in the database
|
||||
- Qdrant has been re-indexed after deployment (or title-based fallback is acceptable for semantic results)
|
||||
|
||||
### Test 1: Key Moment Semantic Search → Parent Page Navigation
|
||||
1. Open http://ub01:8096
|
||||
2. Type "compression" in the search bar
|
||||
3. Wait for results to appear
|
||||
4. Identify a result with type "key_moment" (shown with a moment/clip icon)
|
||||
5. Click the key moment result
|
||||
6. **Expected:** Browser navigates to `/techniques/{parent-technique-slug}#km-{moment-id}` — the parent technique page loads and the page scrolls to the specific key moment in the key moments list
|
||||
|
||||
### Test 2: Key Moment Keyword Search → Parent Page Navigation
|
||||
1. Open http://ub01:8096
|
||||
2. Type a query that matches a key moment title via keyword search (e.g., a specific technique name that exists as a key moment)
|
||||
3. Click the key moment result
|
||||
4. **Expected:** Same as Test 1 — navigates to parent technique page with hash-scroll
|
||||
|
||||
### Test 3: Technique Page Search → Normal Navigation (No Regression)
|
||||
1. Open http://ub01:8096
|
||||
2. Search for a term that matches a technique page title
|
||||
3. Click the technique page result
|
||||
4. **Expected:** Navigates to `/techniques/{slug}` — the technique page loads normally (no hash fragment, no scroll behavior)
|
||||
|
||||
### Test 4: Hash-Scroll Works on Direct URL
|
||||
1. Open a technique page URL directly in the browser
|
||||
2. Find a key moment ID from the page (inspect element on any key moment `<li>` — look for `id="km-{uuid}"`)
|
||||
3. Append `#km-{that-uuid}` to the URL and reload
|
||||
4. **Expected:** Page loads and scrolls smoothly to the specific key moment
|
||||
|
||||
### Test 5: Missing Parent Slug Fallback
|
||||
1. If any key moment in the database has a NULL technique_page_id (orphan moment):
|
||||
- Search for it
|
||||
- Click the result
|
||||
- **Expected:** Browser navigates to a search page with the moment's title as the query — NOT a 404 page
|
||||
|
||||
### Edge Cases
|
||||
- **Empty search results:** Search for gibberish → no results shown, no errors in console
|
||||
- **Key moment at top of page:** If the target moment is the first in the list, scrollIntoView should still work (no-op if already visible)
|
||||
- **Browser back button:** After navigating from search to technique page via key moment link, pressing Back returns to search results
|
||||
16
.gsd/milestones/M008/slices/S01/tasks/T02-VERIFY.json
Normal file
16
.gsd/milestones/M008/slices/S01/tasks/T02-VERIFY.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"taskId": "T02",
|
||||
"unitId": "M008/S01/T02",
|
||||
"timestamp": 1774933445833,
|
||||
"passed": true,
|
||||
"discoverySource": "task-plan",
|
||||
"checks": [
|
||||
{
|
||||
"command": "cd /home/aux/projects/content-to-kb-automator/frontend",
|
||||
"exitCode": 0,
|
||||
"durationMs": 6,
|
||||
"verdict": "pass"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,6 +1,62 @@
|
|||
# S02: Trust & Credibility Cleanup
|
||||
|
||||
**Goal:** Remove test data, dev indicators, and technical jargon from public-facing UI
|
||||
**Goal:** Remove test data, jargon banners, and version display issues that undermine credibility.
|
||||
**Demo:** After this: Creators page has no TestCreator. Footer shows clean version info. Search results have no yellow jargon banner.
|
||||
|
||||
## Tasks
|
||||
- [x] **T01: Added hidden boolean column to Creator model, migration marking TestCreator as hidden, and filtered list_creators() to exclude hidden creators** — Add a `hidden` boolean column to the Creator model with default=False. Create an Alembic migration that adds the column and marks the TestCreator record as hidden. Update the `list_creators()` query and total count query to filter out hidden creators.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Read `backend/models.py` — add `hidden: Mapped[bool]` column to the Creator class with `default=False, server_default='false'`
|
||||
2. Generate Alembic migration: `cd backend && alembic revision --autogenerate -m 'add_creator_hidden_flag'`
|
||||
3. Edit the generated migration to include a data migration step: `UPDATE creators SET hidden = true WHERE slug = 'testcreator'` (or whatever the test creator's slug is — check by reading the existing data migration or searching for 'test' in prior migrations)
|
||||
4. Read `backend/routers/creators.py` — add `.where(Creator.hidden != True)` to the main `list_creators()` query (the `stmt = select(...)` before genre/sort)
|
||||
5. Also add the same filter to the total count query if one exists in the endpoint
|
||||
6. Verify: run `cd backend && python -c "from models import Creator; print('hidden' in [c.key for c in Creator.__table__.columns])"` to confirm the column exists in the model
|
||||
|
||||
## Must-Haves
|
||||
|
||||
- [ ] Creator model has `hidden` boolean column with server_default='false'
|
||||
- [ ] Alembic migration adds column and marks TestCreator as hidden
|
||||
- [ ] list_creators() filters out hidden creators
|
||||
- [ ] Migration file is syntactically valid
|
||||
|
||||
## Verification
|
||||
|
||||
- `cd backend && python -c "from models import Creator; print([c.key for c in Creator.__table__.columns])"` includes 'hidden'
|
||||
- `cd backend && alembic check` reports no pending changes (migration matches model)
|
||||
- Migration file contains both schema change and data UPDATE statement
|
||||
- Estimate: 20m
|
||||
- Files: backend/models.py, backend/routers/creators.py, alembic/versions/
|
||||
- Verify: cd backend && python -c "from models import Creator; print('hidden' in [c.key for c in Creator.__table__.columns])" && alembic check 2>&1 | head -5
|
||||
- [ ] **T02: Remove fallback banner, clean up footer, and bump version** — Three independent frontend cleanups:
|
||||
1. Remove the yellow 'semantic search unavailable' fallback banner from SearchResults
|
||||
2. Clean up the footer to hide commit info when value is 'dev' (already partially done — verify and simplify)
|
||||
3. Bump package.json version to 0.8.0
|
||||
|
||||
## Steps
|
||||
|
||||
1. Read `frontend/src/pages/SearchResults.tsx` — remove the fallback banner JSX block (the `{!loading && fallbackUsed && results.length > 0 && (...)}` section around lines 101-105). Keep the `fallbackUsed` state variable — it's harmless and the API still returns it
|
||||
2. Read `frontend/src/App.css` around line 1119 — remove the `.search-fallback-banner` CSS rule block (approximately lines 1119-1127)
|
||||
3. Read `frontend/src/components/AppFooter.tsx` — the footer already conditionally renders commit as a link vs plain text. Simplify: when `__GIT_COMMIT__` is 'dev', hide the commit section entirely (remove the else branch that shows plain 'dev' text, and the separator before it)
|
||||
4. Edit `frontend/package.json` — change version from '0.1.0' to '0.8.0'
|
||||
5. Run `cd frontend && npm run build` to verify zero build errors
|
||||
|
||||
## Must-Haves
|
||||
|
||||
- [ ] No fallback banner JSX in SearchResults.tsx
|
||||
- [ ] No `.search-fallback-banner` CSS rule in App.css
|
||||
- [ ] Footer hides commit section entirely when __GIT_COMMIT__ is 'dev'
|
||||
- [ ] package.json version is 0.8.0
|
||||
- [ ] `npm run build` succeeds with zero errors
|
||||
|
||||
## Verification
|
||||
|
||||
- `grep -q 'search-fallback-banner' frontend/src/pages/SearchResults.tsx && echo FAIL || echo PASS`
|
||||
- `grep -q 'search-fallback-banner' frontend/src/App.css && echo FAIL || echo PASS`
|
||||
- `grep -q '"0.8.0"' frontend/package.json && echo PASS || echo FAIL`
|
||||
- `cd frontend && npm run build 2>&1 | tail -3`
|
||||
- Estimate: 15m
|
||||
- Files: frontend/src/pages/SearchResults.tsx, frontend/src/App.css, frontend/src/components/AppFooter.tsx, frontend/package.json
|
||||
- Verify: cd frontend && npm run build 2>&1 | tail -5 && grep -q 'search-fallback-banner' src/pages/SearchResults.tsx && echo 'FAIL: banner still present' || echo 'PASS: banner removed'
|
||||
|
|
|
|||
100
.gsd/milestones/M008/slices/S02/S02-RESEARCH.md
Normal file
100
.gsd/milestones/M008/slices/S02/S02-RESEARCH.md
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# S02 Research — Trust & Credibility Cleanup
|
||||
|
||||
## Summary
|
||||
|
||||
Three independent cleanup items, all low-risk, well-understood work using existing patterns. No new technology, no risky integration.
|
||||
|
||||
**Success criteria from roadmap:**
|
||||
1. Creators page has no TestCreator
|
||||
2. Footer shows clean version info
|
||||
3. Search results have no yellow jargon banner
|
||||
|
||||
## Recommendation
|
||||
|
||||
Light research — each item is a small, independent change. No external dependencies or unfamiliar APIs. Straightforward code/config edits.
|
||||
|
||||
## Implementation Landscape
|
||||
|
||||
### Item 1: Remove TestCreator from Creators page
|
||||
|
||||
**Problem:** A test creator record exists in the production PostgreSQL database. The creators list endpoint (`GET /api/v1/creators`) returns all rows from the `creators` table with no filtering — so TestCreator appears on the public Creators page.
|
||||
|
||||
**Current code:**
|
||||
- `backend/routers/creators.py` — `list_creators()` does `select(Creator, ...)` with no visibility filter
|
||||
- `backend/models.py` — `Creator` model has no `hidden`, `is_test`, or `visible` field
|
||||
- `frontend/src/pages/CreatorsBrowse.tsx` — renders whatever the API returns
|
||||
|
||||
**Approach options:**
|
||||
1. **Add `hidden` boolean column to Creator model** + Alembic migration + filter in `list_creators()`. Most robust — supports hiding any creator in the future. Small migration.
|
||||
2. **Admin DELETE endpoint** — add `DELETE /api/v1/creators/{slug}` to remove the test record. Solves the immediate problem but doesn't prevent future test data.
|
||||
3. **Combine both** — add the `hidden` flag for soft-delete semantics AND provide an admin delete endpoint for hard cleanup.
|
||||
|
||||
**Recommended: Option 1 (hidden flag).** A boolean column with `default=False` is a zero-risk migration. The query filter is one `.where(Creator.hidden != True)` clause. This also supports future use cases (temporarily hiding a creator during content review). The admin DELETE can be deferred — the hidden flag is sufficient.
|
||||
|
||||
**Files to change:**
|
||||
- `backend/models.py` — add `hidden: Mapped[bool]` column
|
||||
- `alembic/versions/` — new migration adding `hidden` column with `server_default='false'`
|
||||
- `backend/routers/creators.py` — add `.where(Creator.hidden != True)` to both `list_creators()` and the count query
|
||||
- Mark TestCreator as hidden via a data migration or SQL statement in the migration itself (if the record name is known)
|
||||
|
||||
### Item 2: Footer shows clean version info
|
||||
|
||||
**Problem:** The footer (`frontend/src/components/AppFooter.tsx`) shows version, build date, and git commit via Vite build-time constants. In Docker builds, `VITE_GIT_COMMIT` defaults to `"dev"` if `GIT_COMMIT_SHA` env var isn't set during `docker compose build`. The version comes from `frontend/package.json` (`"0.1.0"`).
|
||||
|
||||
**Current state:**
|
||||
- `frontend/vite.config.ts` — defines `__APP_VERSION__`, `__BUILD_DATE__`, `__GIT_COMMIT__` via `define:{}`
|
||||
- `docker/Dockerfile.web` — accepts `ARG VITE_GIT_COMMIT=dev`, passes to env
|
||||
- `docker-compose.yml` — `args: VITE_GIT_COMMIT: ${GIT_COMMIT_SHA:-dev}`
|
||||
- `frontend/src/components/AppFooter.tsx` — renders `v{version} · Built {date} · {commit} · GitHub`
|
||||
|
||||
**What "clean" means:** The footer should show a real commit hash (not "dev") and the version should be meaningful. The build date is fine as-is.
|
||||
|
||||
**Approach:**
|
||||
1. Update `docker-compose.yml` build args to compute `GIT_COMMIT_SHA` at build time: replace `${GIT_COMMIT_SHA:-dev}` with a shell command that runs `git rev-parse --short HEAD` (or make the build script set it).
|
||||
2. In `Dockerfile.web`, the fallback already handles local dev ("dev" is fine there).
|
||||
3. Optionally — if the commit is "dev", hide the commit section in the footer rather than showing the literal word "dev".
|
||||
4. Bump `frontend/package.json` version to reflect the current milestone (e.g., `0.8.0` for M008).
|
||||
|
||||
**Files to change:**
|
||||
- `frontend/src/components/AppFooter.tsx` — conditionally hide commit when "dev"
|
||||
- `frontend/package.json` — bump version
|
||||
- `docker-compose.yml` — improve GIT_COMMIT_SHA default (optional, could also be done in a deploy script)
|
||||
|
||||
### Item 3: Remove yellow jargon banner from search results
|
||||
|
||||
**Problem:** When Qdrant semantic search fails and the keyword fallback is used, a yellow/amber banner appears: "Showing keyword results — semantic search unavailable". This is developer-facing diagnostic text, not user-appropriate messaging.
|
||||
|
||||
**Current code:**
|
||||
- `backend/routers/search.py` — returns `fallback_used: bool` in `SearchResponse`
|
||||
- `backend/search_service.py` — sets `fallback_used = True` when Qdrant/embedding fails (line 292)
|
||||
- `backend/schemas.py` — `SearchResponse.fallback_used: bool = False`
|
||||
- `frontend/src/pages/SearchResults.tsx` — renders `.search-fallback-banner` when `fallbackUsed && results.length > 0`
|
||||
- `frontend/src/App.css` line 1119 — styles using `--color-banner-amber-bg/border/text`
|
||||
|
||||
**Approach options:**
|
||||
1. **Remove the banner entirely** — simplest. Users don't need to know which search backend served results. The results still appear either way.
|
||||
2. **Reword to user-friendly text** — e.g., "Showing approximate matches" or nothing at all.
|
||||
3. **Move to admin-only visibility** — show the banner only if an admin flag/cookie is set.
|
||||
|
||||
**Recommended: Option 1 (remove entirely).** The banner provides no actionable information to end users. If semantic search is down, keyword results still work. Admin can check system health via the pipeline dashboard or health endpoint. Keep `fallback_used` in the API response (it's useful for monitoring) but don't render it in the UI.
|
||||
|
||||
**Files to change:**
|
||||
- `frontend/src/pages/SearchResults.tsx` — remove the fallback banner JSX block (lines ~99-103)
|
||||
- `frontend/src/App.css` — remove `.search-fallback-banner` styles (lines 1119-1127) — optional cleanup
|
||||
|
||||
## Seams / Task Decomposition
|
||||
|
||||
All three items are fully independent — they can be done in any order or in parallel. Natural task split:
|
||||
|
||||
1. **T01: Hidden flag + migration + filter** — backend model change, migration, query filter update
|
||||
2. **T02: Footer cleanup** — frontend-only, version bump + conditional commit display
|
||||
3. **T03: Remove fallback banner** — frontend-only, remove JSX and CSS
|
||||
|
||||
T01 is the only one touching the backend/database. T02 and T03 are purely frontend. All three are small (15-30 min each).
|
||||
|
||||
## Verification
|
||||
|
||||
- **T01:** `curl` the creators endpoint and confirm no TestCreator in results. Alternatively, check the live site at http://ub01:8096/creators.
|
||||
- **T02:** Build frontend, check footer renders version without "dev" commit. In Docker: `docker compose build chrysopedia-web` then verify footer.
|
||||
- **T03:** Build frontend, trigger a search. The banner should not appear even if `fallback_used` is true in the API response. Can verify with browser dev tools by mocking the API response.
|
||||
- **Full slice:** `npm run build` in frontend with zero errors. Backend tests pass. Docker compose builds cleanly.
|
||||
46
.gsd/milestones/M008/slices/S02/tasks/T01-PLAN.md
Normal file
46
.gsd/milestones/M008/slices/S02/tasks/T01-PLAN.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
estimated_steps: 17
|
||||
estimated_files: 3
|
||||
skills_used: []
|
||||
---
|
||||
|
||||
# T01: Add hidden flag to Creator model and filter TestCreator from API
|
||||
|
||||
Add a `hidden` boolean column to the Creator model with default=False. Create an Alembic migration that adds the column and marks the TestCreator record as hidden. Update the `list_creators()` query and total count query to filter out hidden creators.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Read `backend/models.py` — add `hidden: Mapped[bool]` column to the Creator class with `default=False, server_default='false'`
|
||||
2. Generate Alembic migration: `cd backend && alembic revision --autogenerate -m 'add_creator_hidden_flag'`
|
||||
3. Edit the generated migration to include a data migration step: `UPDATE creators SET hidden = true WHERE slug = 'testcreator'` (or whatever the test creator's slug is — check by reading the existing data migration or searching for 'test' in prior migrations)
|
||||
4. Read `backend/routers/creators.py` — add `.where(Creator.hidden != True)` to the main `list_creators()` query (the `stmt = select(...)` before genre/sort)
|
||||
5. Also add the same filter to the total count query if one exists in the endpoint
|
||||
6. Verify: run `cd backend && python -c "from models import Creator; print('hidden' in [c.key for c in Creator.__table__.columns])"` to confirm the column exists in the model
|
||||
|
||||
## Must-Haves
|
||||
|
||||
- [ ] Creator model has `hidden` boolean column with server_default='false'
|
||||
- [ ] Alembic migration adds column and marks TestCreator as hidden
|
||||
- [ ] list_creators() filters out hidden creators
|
||||
- [ ] Migration file is syntactically valid
|
||||
|
||||
## Verification
|
||||
|
||||
- `cd backend && python -c "from models import Creator; print([c.key for c in Creator.__table__.columns])"` includes 'hidden'
|
||||
- `cd backend && alembic check` reports no pending changes (migration matches model)
|
||||
- Migration file contains both schema change and data UPDATE statement
|
||||
|
||||
## Inputs
|
||||
|
||||
- ``backend/models.py` — Creator model to add hidden column to`
|
||||
- ``backend/routers/creators.py` — list_creators endpoint to add filter to`
|
||||
|
||||
## Expected Output
|
||||
|
||||
- ``backend/models.py` — Creator model with hidden boolean column`
|
||||
- ``backend/routers/creators.py` — list_creators filtering hidden creators`
|
||||
- ``alembic/versions/*_add_creator_hidden_flag.py` — migration adding column + marking TestCreator hidden`
|
||||
|
||||
## Verification
|
||||
|
||||
cd backend && python -c "from models import Creator; print('hidden' in [c.key for c in Creator.__table__.columns])" && alembic check 2>&1 | head -5
|
||||
80
.gsd/milestones/M008/slices/S02/tasks/T01-SUMMARY.md
Normal file
80
.gsd/milestones/M008/slices/S02/tasks/T01-SUMMARY.md
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
---
|
||||
id: T01
|
||||
parent: S02
|
||||
milestone: M008
|
||||
provides: []
|
||||
requires: []
|
||||
affects: []
|
||||
key_files: ["backend/models.py", "backend/routers/creators.py", "alembic/versions/009_add_creator_hidden_flag.py"]
|
||||
key_decisions: ["Manual migration instead of autogenerate (no DB connection from dev machine)"]
|
||||
patterns_established: []
|
||||
drill_down_paths: []
|
||||
observability_surfaces: []
|
||||
duration: ""
|
||||
verification_result: "Model column verified present via Python import check. Migration and router files verified syntactically valid via ast.parse. Migration confirmed to contain both schema change (add_column) and data migration (UPDATE). alembic check deferred to deployment on ub01."
|
||||
completed_at: 2026-03-31T05:13:14.423Z
|
||||
blocker_discovered: false
|
||||
---
|
||||
|
||||
# T01: Added hidden boolean column to Creator model, migration marking TestCreator as hidden, and filtered list_creators() to exclude hidden creators
|
||||
|
||||
> Added hidden boolean column to Creator model, migration marking TestCreator as hidden, and filtered list_creators() to exclude hidden creators
|
||||
|
||||
## What Happened
|
||||
---
|
||||
id: T01
|
||||
parent: S02
|
||||
milestone: M008
|
||||
key_files:
|
||||
- backend/models.py
|
||||
- backend/routers/creators.py
|
||||
- alembic/versions/009_add_creator_hidden_flag.py
|
||||
key_decisions:
|
||||
- Manual migration instead of autogenerate (no DB connection from dev machine)
|
||||
duration: ""
|
||||
verification_result: passed
|
||||
completed_at: 2026-03-31T05:13:14.423Z
|
||||
blocker_discovered: false
|
||||
---
|
||||
|
||||
# T01: Added hidden boolean column to Creator model, migration marking TestCreator as hidden, and filtered list_creators() to exclude hidden creators
|
||||
|
||||
**Added hidden boolean column to Creator model, migration marking TestCreator as hidden, and filtered list_creators() to exclude hidden creators**
|
||||
|
||||
## What Happened
|
||||
|
||||
Added `hidden: Mapped[bool]` column to Creator model with server_default='false'. Wrote migration 009_add_creator_hidden_flag.py that adds the column and marks slug='testcreator' as hidden. Updated list_creators() in creators router to filter Creator.hidden != True on both the main query and total count query. Migration was written manually since the database runs on ub01 and isn't accessible from this dev machine.
|
||||
|
||||
## Verification
|
||||
|
||||
Model column verified present via Python import check. Migration and router files verified syntactically valid via ast.parse. Migration confirmed to contain both schema change (add_column) and data migration (UPDATE). alembic check deferred to deployment on ub01.
|
||||
|
||||
## Verification Evidence
|
||||
|
||||
| # | Command | Exit Code | Verdict | Duration |
|
||||
|---|---------|-----------|---------|----------|
|
||||
| 1 | `python -c "from models import Creator; print('hidden' in [c.key for c in Creator.__table__.columns])"` | 0 | ✅ pass | 500ms |
|
||||
| 2 | `python -c "import ast; ast.parse(open('alembic/versions/009_add_creator_hidden_flag.py').read())"` | 0 | ✅ pass | 200ms |
|
||||
| 3 | `python -c "import ast; ast.parse(open('routers/creators.py').read())"` | 0 | ✅ pass | 200ms |
|
||||
|
||||
|
||||
## Deviations
|
||||
|
||||
Migration written manually instead of via alembic revision --autogenerate due to no DB connection from dev machine.
|
||||
|
||||
## Known Issues
|
||||
|
||||
alembic check cannot run locally — must verify model-migration sync on ub01.
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `backend/models.py`
|
||||
- `backend/routers/creators.py`
|
||||
- `alembic/versions/009_add_creator_hidden_flag.py`
|
||||
|
||||
|
||||
## Deviations
|
||||
Migration written manually instead of via alembic revision --autogenerate due to no DB connection from dev machine.
|
||||
|
||||
## Known Issues
|
||||
alembic check cannot run locally — must verify model-migration sync on ub01.
|
||||
53
.gsd/milestones/M008/slices/S02/tasks/T02-PLAN.md
Normal file
53
.gsd/milestones/M008/slices/S02/tasks/T02-PLAN.md
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
estimated_steps: 21
|
||||
estimated_files: 4
|
||||
skills_used: []
|
||||
---
|
||||
|
||||
# T02: Remove fallback banner, clean up footer, and bump version
|
||||
|
||||
Three independent frontend cleanups:
|
||||
1. Remove the yellow 'semantic search unavailable' fallback banner from SearchResults
|
||||
2. Clean up the footer to hide commit info when value is 'dev' (already partially done — verify and simplify)
|
||||
3. Bump package.json version to 0.8.0
|
||||
|
||||
## Steps
|
||||
|
||||
1. Read `frontend/src/pages/SearchResults.tsx` — remove the fallback banner JSX block (the `{!loading && fallbackUsed && results.length > 0 && (...)}` section around lines 101-105). Keep the `fallbackUsed` state variable — it's harmless and the API still returns it
|
||||
2. Read `frontend/src/App.css` around line 1119 — remove the `.search-fallback-banner` CSS rule block (approximately lines 1119-1127)
|
||||
3. Read `frontend/src/components/AppFooter.tsx` — the footer already conditionally renders commit as a link vs plain text. Simplify: when `__GIT_COMMIT__` is 'dev', hide the commit section entirely (remove the else branch that shows plain 'dev' text, and the separator before it)
|
||||
4. Edit `frontend/package.json` — change version from '0.1.0' to '0.8.0'
|
||||
5. Run `cd frontend && npm run build` to verify zero build errors
|
||||
|
||||
## Must-Haves
|
||||
|
||||
- [ ] No fallback banner JSX in SearchResults.tsx
|
||||
- [ ] No `.search-fallback-banner` CSS rule in App.css
|
||||
- [ ] Footer hides commit section entirely when __GIT_COMMIT__ is 'dev'
|
||||
- [ ] package.json version is 0.8.0
|
||||
- [ ] `npm run build` succeeds with zero errors
|
||||
|
||||
## Verification
|
||||
|
||||
- `grep -q 'search-fallback-banner' frontend/src/pages/SearchResults.tsx && echo FAIL || echo PASS`
|
||||
- `grep -q 'search-fallback-banner' frontend/src/App.css && echo FAIL || echo PASS`
|
||||
- `grep -q '"0.8.0"' frontend/package.json && echo PASS || echo FAIL`
|
||||
- `cd frontend && npm run build 2>&1 | tail -3`
|
||||
|
||||
## Inputs
|
||||
|
||||
- ``frontend/src/pages/SearchResults.tsx` — contains fallback banner to remove`
|
||||
- ``frontend/src/App.css` — contains .search-fallback-banner CSS to remove`
|
||||
- ``frontend/src/components/AppFooter.tsx` — footer component to simplify`
|
||||
- ``frontend/package.json` — version to bump`
|
||||
|
||||
## Expected Output
|
||||
|
||||
- ``frontend/src/pages/SearchResults.tsx` — fallback banner JSX removed`
|
||||
- ``frontend/src/App.css` — .search-fallback-banner CSS rule removed`
|
||||
- ``frontend/src/components/AppFooter.tsx` — commit section hidden when 'dev'`
|
||||
- ``frontend/package.json` — version set to 0.8.0`
|
||||
|
||||
## Verification
|
||||
|
||||
cd frontend && npm run build 2>&1 | tail -5 && grep -q 'search-fallback-banner' src/pages/SearchResults.tsx && echo 'FAIL: banner still present' || echo 'PASS: banner removed'
|
||||
28
alembic/versions/009_add_creator_hidden_flag.py
Normal file
28
alembic/versions/009_add_creator_hidden_flag.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
"""Add hidden boolean flag to creators table.
|
||||
|
||||
Marks test/internal creators as hidden so they are filtered from
|
||||
public API responses.
|
||||
|
||||
Revision ID: 009_add_creator_hidden_flag
|
||||
Revises: 008_rename_processing_status
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = "009_add_creator_hidden_flag"
|
||||
down_revision = "008_rename_processing_status"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"creators",
|
||||
sa.Column("hidden", sa.Boolean(), server_default="false", nullable=False),
|
||||
)
|
||||
# Mark known test creator as hidden
|
||||
op.execute("UPDATE creators SET hidden = true WHERE slug = 'testcreator'")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("creators", "hidden")
|
||||
|
|
@ -104,6 +104,7 @@ class Creator(Base):
|
|||
genres: Mapped[list[str] | None] = mapped_column(ARRAY(String), nullable=True)
|
||||
folder_name: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
view_count: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
||||
hidden: Mapped[bool] = mapped_column(default=False, server_default="false")
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
default=_now, server_default=func.now()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ async def list_creators(
|
|||
Creator,
|
||||
technique_count_sq.label("technique_count"),
|
||||
video_count_sq.label("video_count"),
|
||||
)
|
||||
).where(Creator.hidden != True) # noqa: E712
|
||||
|
||||
# Genre filter
|
||||
if genre:
|
||||
|
|
@ -81,7 +81,7 @@ async def list_creators(
|
|||
)
|
||||
|
||||
# Get total count (without offset/limit)
|
||||
count_stmt = select(func.count()).select_from(Creator)
|
||||
count_stmt = select(func.count()).select_from(Creator).where(Creator.hidden != True) # noqa: E712
|
||||
if genre:
|
||||
count_stmt = count_stmt.where(Creator.genres.any(genre))
|
||||
total = (await db.execute(count_stmt)).scalar() or 0
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue