From 7d5fb35964b610b3181a23096d76b45f380ac539 Mon Sep 17 00:00:00 2001 From: Madhura68 Date: Fri, 15 May 2026 17:07:19 +0200 Subject: [PATCH 1/4] docs(PBI-86): hybride Forgejo-flow gedocumenteerd (T-1008) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Werker-flow docs aangepast naar het hybride model: `origin` is Forgejo, de worker maakt geen GitHub-PR meer; die ontstaat via een handmatig getriggerde promote-Action in Forgejo. - docs/runbooks/forgejo-hybrid-flow.md: nieuw canoniek runbook met het flow-diagram, de promote-stap en main-sync. - CLAUDE.md: stap 1 + stap 8 + Hardstop "Push" verwijzen naar het nieuwe runbook. - AGENTS.md: tabel-rij "Queue leeg" + PBI-86-toelichting. - docs/runbooks/branch-and-commit.md: intro herschreven (Vercel-cost verschuift naar promote-trigger), agent-batch-flow tabel + E2E- verificatie + rebase-noot bijgewerkt. - docs/runbooks/auto-pr-flow.md: DEPRECATED-banner; document blijft als historische referentie. - docs/manual/03-git-workflow.md: rule 3 (push), rule 5 (auto-PR → promote) en de merge-conflicts tabel. - docs/manual/04-mcp-integration.md: mermaid-stap aangepast. - docs/manual/05-docker.md: GITHUB_TOKEN-beschrijving verduidelijkt (optioneel; niet meer nodig voor de worker zelf). - docs/INDEX.md: regen via npm run docs (113 docs). npm run verify groen: 924 tests, typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- AGENTS.md | 7 ++- CLAUDE.md | 6 +-- docs/INDEX.md | 1 + docs/manual/03-git-workflow.md | 11 ++-- docs/manual/04-mcp-integration.md | 2 +- docs/manual/05-docker.md | 2 +- docs/runbooks/auto-pr-flow.md | 10 ++++ docs/runbooks/branch-and-commit.md | 43 ++++++++------- docs/runbooks/forgejo-hybrid-flow.md | 80 ++++++++++++++++++++++++++++ 9 files changed, 131 insertions(+), 31 deletions(-) create mode 100644 docs/runbooks/forgejo-hybrid-flow.md diff --git a/AGENTS.md b/AGENTS.md index 6d98658..cef53e7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,6 +18,11 @@ For Claude Code specifically, CLAUDE.md is loaded automatically. Start there. |---|---|---| | Start run | `git checkout -b feat/` | `gh pr create` | | Na elke taak | `git add -A && git commit -m "(ST-XXX): "` | `git push` | -| Queue leeg | `git push -u origin <branch>` + `gh pr create` | — | +| Queue leeg | `git push -u origin <branch>` (Forgejo) | `gh pr create` — GitHub-PR via de promote-Action | + +Sinds **PBI-86** (hybride Forgejo-model) is `origin` Forgejo. De worker +initieert geen GitHub-PR meer — die ontstaat via een handmatig getriggerde +promote-Action in Forgejo. Zie +[docs/runbooks/forgejo-hybrid-flow.md](./docs/runbooks/forgejo-hybrid-flow.md). Full details: [docs/runbooks/branch-and-commit.md § Agent-batch flow](./docs/runbooks/branch-and-commit.md) diff --git a/CLAUDE.md b/CLAUDE.md index 06dc2fb..5e59247 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -29,14 +29,14 @@ Desktop-first Scrum-app voor solo developers en kleine teams. Hiërarchie: produ ## Hoe werk vinden -1. Branch aanmaken: `git checkout -b feat/<batch-slug>` — nog **geen** `gh pr create` +1. Branch aanmaken: `git checkout -b feat/<batch-slug>` — `gh pr create` komt niet meer voor in de worker-flow (PBI-86, zie [forgejo-hybrid-flow.md](./docs/runbooks/forgejo-hybrid-flow.md)) 2. `mcp__scrum4me__get_claude_context` → pak de next story 3. Voer taken uit in `sort_order`; update status per taak 4. Lees het relevante patroon en styling vóór je begint 5. Verifieer: `npm run verify && npm run build` — `verify` = lint + typecheck + test 6. Commit per laag: `git add -A && git commit` — **geen** `git push` — zie [docs/runbooks/branch-and-commit.md](./docs/runbooks/branch-and-commit.md) 7. Herhaal stap 2–6 per story; branch blijft dezelfde -8. Queue leeg → `git push -u origin <branch>` + `gh pr create` +8. Queue leeg → `git push -u origin <branch>` (Forgejo). De GitHub-PR ontstaat los via de handmatig getriggerde promote-Action — zie [forgejo-hybrid-flow.md](./docs/runbooks/forgejo-hybrid-flow.md) Volledige MCP-tool documentatie: [docs/runbooks/mcp-integration.md](./docs/runbooks/mcp-integration.md) @@ -46,7 +46,7 @@ Volledige MCP-tool documentatie: [docs/runbooks/mcp-integration.md](./docs/runbo - **Styling:** nooit `bg-blue-500`; altijd MD3-tokens (`bg-primary`, `bg-status-done`, …) - **UI:** gebruik `@base-ui/react` met `render`-prop, niet Radix `asChild` -- **Push:** commits accumuleren lokaal per taak (`git add -A && git commit`); push + PR pas bij lege queue of na expliciete gebruikersbevestiging — zie [branch-and-commit.md](./docs/runbooks/branch-and-commit.md) +- **Push:** commits accumuleren lokaal per taak (`git add -A && git commit`); push naar **Forgejo** (`origin`) pas bij lege queue of na expliciete gebruikersbevestiging. GitHub-PR via de promote-Action (geen `gh pr create` vanuit de worker) — zie [forgejo-hybrid-flow.md](./docs/runbooks/forgejo-hybrid-flow.md) en [branch-and-commit.md](./docs/runbooks/branch-and-commit.md) - **Demo:** drie lagen — proxy.ts + server action + UI disabled knop - **Proxy:** `proxy.ts` in repo-root (géén `middleware.ts`) onverzegelt de iron-session, redirect niet-geauthenticeerde users op `/dashboard|/products|/ideas`, en blokkeert niet-GET API-writes voor demo-users behalve `/api/cron/*` - **Enum:** DB UPPER_SNAKE ↔ API lowercase — uitsluitend via `lib/task-status.ts` diff --git a/docs/INDEX.md b/docs/INDEX.md index ffcbce3..d25b5ca 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -134,6 +134,7 @@ Auto-generated on 2026-05-15 from front-matter and headings. | [Branch, PR & Commit Strategy](./runbooks/branch-and-commit.md) | `runbooks/branch-and-commit.md` | active | 2026-05-03 | | [Deploy-controle: triggers, labels, path-filter](./runbooks/deploy-control.md) | `runbooks/deploy-control.md` | active | 2026-05-07 | | [Vercel Deployment](./runbooks/deploy-vercel.md) | `runbooks/deploy-vercel.md` | active | 2026-05-03 | +| [Forgejo hybride model — worker pusht intern, promote naar GitHub](./runbooks/forgejo-hybrid-flow.md) | `runbooks/forgejo-hybrid-flow.md` | active | 2026-05-15 | | [Job-model-selectie per ClaudeJob-kind](./runbooks/job-model-selection.md) | `runbooks/job-model-selection.md` | active | 2026-05-09 (idea-kinds + PLAN_CHAT permission_mode → acceptEdits) | | [MCP Integration — Scrum4Me Tools](./runbooks/mcp-integration.md) | `runbooks/mcp-integration.md` | active | 2026-05-08 | | [Plan → Sprint/PBI/Story/Task workflow](./runbooks/plan-to-pbi-flow.md) | `runbooks/plan-to-pbi-flow.md` | active | 2026-05-11 | diff --git a/docs/manual/03-git-workflow.md b/docs/manual/03-git-workflow.md index 888c7f1..32b0a97 100644 --- a/docs/manual/03-git-workflow.md +++ b/docs/manual/03-git-workflow.md @@ -42,7 +42,7 @@ docs(ST-XXX): document the X feature # docs ### 3. Push only after the user has tested -Commits accumulate **locally** until the milestone is functionally complete and the user has confirmed it works. Then — and only then — `git push` and `gh pr create`. +Commits accumulate **locally** until the milestone is functionally complete and the user has confirmed it works. Then — and only then — `git push` (to Forgejo, the worker's `origin` since PBI-86). The GitHub-PR comes later via the manual promote-Action — see [`forgejo-hybrid-flow.md`](../runbooks/forgejo-hybrid-flow.md). > **Why?** Same cost reason as rule 1. Mid-milestone "save points" should be local tags or `git stash`, not pushes. Some exceptions exist (planning-only PRs, emergency hotfixes); they're enumerated in [`branch-and-commit.md`](../runbooks/branch-and-commit.md#uitzonderingen-op-de-push-regel). @@ -52,12 +52,13 @@ When the worker runs through a queue of jobs, the entire run produces **one** PR The end-to-end verification — that one batch produces exactly one Vercel deployment — is in [`branch-and-commit.md`](../runbooks/branch-and-commit.md) (see the *End-to-end verificatie* section). -### 5. Auto-PR flow at the end +### 5. Promote to GitHub at the end (PBI-86 hybrid model) -Once a story reaches `DONE`, the auto-PR flow takes over: it pushes the branch, opens a PR, waits for the scope to be complete, waits for checks, and merges. The contract for "scope complete" and the path-filter / label rules that decide whether a deploy actually runs are split between two runbooks: +Once a story reaches `DONE` and the branch is pushed to Forgejo, a human operator triggers the promote-Action in Forgejo to land the branch on GitHub and open the PR. Path-filter / label rules then decide whether a Vercel deploy runs. -- **End-to-end pipeline**: [`docs/runbooks/auto-pr-flow.md`](../runbooks/auto-pr-flow.md) +- **Hybrid model + promote flow**: [`docs/runbooks/forgejo-hybrid-flow.md`](../runbooks/forgejo-hybrid-flow.md) - **Selective deploy controls** (`skip-deploy` label, path-filter for `app/`/`components/`/`lib/`): [`docs/runbooks/deploy-control.md`](../runbooks/deploy-control.md) +- **Historical auto-PR flow** (removed in T-1005…T-1007): [`docs/runbooks/auto-pr-flow.md`](../runbooks/auto-pr-flow.md) ## Commit message format @@ -75,7 +76,7 @@ For PBI-level work (no single story), use the PBI code: `docs(PBI-58): scaffold |---|---|---| | Multiple tasks on the same batch branch | No — they stack linearly on one branch | None needed | | Two parallel batches touching the same files | Yes, possible | Serialise batches via the MCP `get_claude_context` flow (one story at a time per agent), or rebase before push | -| Long-lived branch drifting from `main` | Yes, possible | `git fetch origin main && git rebase origin/main` before `gh pr create` | +| Long-lived branch drifting from `main` | Yes, possible | `git fetch origin main && git rebase origin/main` before push (and before promote to GitHub) | `git push --force` to "wipe" earlier preview builds is forbidden — it costs the same build again on recreation, defeating the purpose of the cost-control rules. diff --git a/docs/manual/04-mcp-integration.md b/docs/manual/04-mcp-integration.md index 5860621..5fdc429 100644 --- a/docs/manual/04-mcp-integration.md +++ b/docs/manual/04-mcp-integration.md @@ -46,7 +46,7 @@ sequenceDiagram end C->>U: "milestone ready for your test" U->>C: "looks good, push it" - C->>C: git push + gh pr create + C->>C: git push (origin=Forgejo) — GitHub-PR via promote-Action ``` The contract every step relies on: diff --git a/docs/manual/05-docker.md b/docs/manual/05-docker.md index 4400160..4d2f40e 100644 --- a/docs/manual/05-docker.md +++ b/docs/manual/05-docker.md @@ -57,7 +57,7 @@ The worker container needs **only** what's required to authenticate to MCP and p |---|---| | `SCRUM4ME_BEARER_TOKEN` | Bearer token bound to a product. Returned by the user's API-token settings page. | | `SCRUM4ME_BASE_URL` | Usually `https://scrum4me.vercel.app` (or the user's domain). | -| `GITHUB_TOKEN` | Personal access token with `repo` scope, used by `git push` and `gh pr create`. | +| `GITHUB_TOKEN` | Optional. No longer needed by the worker itself — `git push` goes to Forgejo (`origin`) since PBI-86, and `gh pr create` was removed from the worker flow (T-1005). The promote-Action has its own `GH_PROMOTE_TOKEN` configured in Forgejo. | | `ANTHROPIC_API_KEY` | The Claude API key used by the worker process. | | `MIN_QUOTA_PCT` | Optional. Worker pauses if Anthropic quota drops below this percentage. | diff --git a/docs/runbooks/auto-pr-flow.md b/docs/runbooks/auto-pr-flow.md index 710965f..5b2fb27 100644 --- a/docs/runbooks/auto-pr-flow.md +++ b/docs/runbooks/auto-pr-flow.md @@ -9,6 +9,16 @@ when_to_read: "Vóór het aanzetten van auto_pr op een product, of bij debugging # Auto-PR flow +> ⚠️ **DEPRECATED — PBI-86 (hybride Forgejo-model)** +> +> De auto-PR-keten beschreven in dit runbook is **verwijderd** uit +> `scrum4me-mcp` (T-1005…T-1007). De worker maakt geen GitHub-PR's meer, +> en de auto-merge / mark-ready / revert-cascade machinerie is weg. De +> huidige flow staat in [forgejo-hybrid-flow.md](./forgejo-hybrid-flow.md): +> de worker pusht naar Forgejo, een handmatige promote-Action zet de +> branch + PR op GitHub. Dit document blijft als historische referentie +> voor de pre-PBI-86 keten. + Wanneer een Scrum4Me-agent een TASK_IMPLEMENTATION-job afrondt, kan de hele keten **commit → push → PR → auto-merge → deploy** zonder handmatige actie verlopen — mits het bijbehorende product `auto_pr=true` diff --git a/docs/runbooks/branch-and-commit.md b/docs/runbooks/branch-and-commit.md index 0e5ddbc..5090fb9 100644 --- a/docs/runbooks/branch-and-commit.md +++ b/docs/runbooks/branch-and-commit.md @@ -13,7 +13,13 @@ when_to_read: "Before creating a branch, commit, or PR. Also before any agent-ba > **Core rule: één branch per milestone, PR alleen na gebruikerstest** -Elke `git push` naar een feature-branch triggert een Vercel preview-deployment. Op het huidige Hobby-account zijn die schaars en kosten geld; we minimaliseren preview-builds tot er werkelijk iets te reviewen valt. +Sinds **PBI-86** is `origin` voor de Scrum4Me-repos de zelf-gehoste Forgejo +(zie [forgejo-hybrid-flow.md](./forgejo-hybrid-flow.md)). De worker pusht +daar naartoe — die push triggert géén Vercel-deploy. Vercel-previews +ontstaan pas wanneer je de **promote-Action** in Forgejo handmatig triggert, +die op zijn beurt een branch + PR op GitHub aanmaakt. Op het huidige +Hobby-account zijn previews schaars en kosten geld; we minimaliseren ze tot +er werkelijk iets te reviewen valt. ### Wel doen @@ -53,38 +59,35 @@ Wanneer de NAS-agent (`/opt/agent/`) een batch jobs uitvoert: |---|---|---| | Start run | `git checkout -b feat/<batch-slug>` lokaal | `gh pr create` | | Na elke taak | `git add -A && git commit -m "<type>(ST-XXX): <title>"` | `git push` | -| Queue leeg | `git push -u origin <branch>` + `gh pr create` | — | +| Queue leeg | `git push -u origin <branch>` (Forgejo) | `gh pr create` — GitHub-PR via de promote-Action | - Alle commits accumuleren op dezelfde branch — lopende state blijft op disk tot de run klaar is. - Één PR per batch → één Vercel preview-deployment. - Single-task batch (1 job in queue): dezelfde flow — 1 commit → push + PR. -#### End-to-end verificatie: 1 batch = 1 Vercel-deploy +#### End-to-end verificatie -Gebruik deze checklist om te verifiëren dat de batch-flow correct werkt na een agent-run: +Gebruik deze checklist om te verifiëren dat de batch-flow correct werkt na +een agent-run: **Voorbereiding** 1. Seed ≥ 2 taken onder één story (bv. README-edits). 2. Trigger de batch via **"Voer alle uit"** op het Solo Board. 3. Wacht tot de agent alle jobs als `done` markeert. -**GitHub-checks** -- [ ] Er is precies **één PR** aangemaakt voor de batch-branch. -- [ ] De PR bevat **één commit per taak** (geen squash, geen force-push). -- [ ] Er zijn **geen losse pushes** op de branch vóór de definitieve push (check via `git log --all --graph` of GitHub's "commits" tab). +**Forgejo-checks** (worker-output) +- [ ] Er is precies **één branch** op Forgejo voor de batch (`feat/<batch-slug>`). +- [ ] De branch bevat **één commit per taak** (geen squash, geen force-push). +- [ ] Er is **geen** GitHub-PR vanuit de worker aangemaakt. -**Vercel-checks** -- [ ] In het Vercel-dashboard → **Deployments**: er is **exact één preview-deployment** voor de branch in het run-window. -- [ ] Geen extra "cancelled" of "building" deployments voor dezelfde branch uit hetzelfde tijdsvenster (zou wijzen op tussentijdse pushes). +**Pas na promote** (handmatig via Forgejo Action — zie [forgejo-hybrid-flow.md](./forgejo-hybrid-flow.md)) +- [ ] Eén GitHub-PR voor de gepromote branch. +- [ ] Vercel-dashboard → Deployments: **exact één preview-deployment** voor die PR. -**Alternatieve verificatie via Vercel MCP** (indien beschikbaar): -``` -mcp__<vercel-plugin-id>__list_deployments - → filter op branchName = feat/<batch-slug> - → verwacht: 1 entry met state = READY of BUILDING -``` - -**Race-condition scenario**: als een nieuwe taak in de queue terechtkomt terwijl de agent de queue-check uitvoert, kan er een tweede push volgen. Dit is acceptabel — de tweede push triggert een tweede deployment voor de resterende commits. Documenteer dit afwijkend gedrag in de PR-description als het zich voordoet. +**Race-condition scenario**: als een nieuwe taak in de queue terechtkomt +terwijl de agent de queue-check uitvoert, kan er een tweede push (naar +Forgejo) volgen. Acceptabel — de extra commits komen vanzelf mee bij de +volgende promote. ### Merge conflicten — wanneer wel/niet? @@ -97,7 +100,7 @@ Een veelgestelde vraag: "als meerdere stories dezelfde bestanden raken, krijgen Mitigaties: 1. **Seriële PRs** — start een nieuwe batch pas als de vorige PR gemerged is. De MCP `get_claude_context`-flow stuurt hier al op (één story tegelijk per agent). 2. **Slim batchen** — stories die hetzelfde domein raken (bv. alles rond Sprint Board) horen in dezelfde batch, niet verspreid over batches. -3. **Rebase vóór push** — `git fetch origin main && git rebase origin/main` vóór `gh pr create` lost kleine drift op zonder conflict. +3. **Rebase vóór push** — `git fetch origin main && git rebase origin/main` vóór de push (en vóór een promote) lost kleine drift op zonder conflict. --- diff --git a/docs/runbooks/forgejo-hybrid-flow.md b/docs/runbooks/forgejo-hybrid-flow.md new file mode 100644 index 0000000..05632b5 --- /dev/null +++ b/docs/runbooks/forgejo-hybrid-flow.md @@ -0,0 +1,80 @@ +--- +title: "Forgejo hybride model — worker pusht intern, promote naar GitHub" +status: active +audience: [contributor, ai-agent] +language: nl +last_updated: 2026-05-15 +when_to_read: "Vóór elke agent-batch op een Scrum4Me-product, en wanneer je een branch op GitHub wilt landen." +--- + +# Forgejo hybride model + +Sinds **PBI-86** is `origin` voor de Scrum4Me-repos de zelf-gehoste Forgejo +op `https://git.jp-visser.nl/`. De worker pusht alleen daar naartoe; GitHub +blijft de publieke kant en de PR-/Vercel-gateway. Vercel-previews ontstaan +**niet** meer automatisch op een worker-push. + +## Wat de worker doet + +``` +MacBook / AI-worker + │ git push origin <branch> + ▼ +Forgejo (intern, publiek + tailnet) +``` + +- Branch `feat/<batch-slug>` op Forgejo. +- Commits per laag (`git add -A && git commit`). +- Bij lege queue: `git push -u origin <branch>`. **Geen** `gh pr create`. +- `update_job_status('done')` sluit de taak/story af in de Scrum4Me-DB. + +Dat is het. Geen worker-side GitHub-PR meer, geen auto-merge, geen draft-PR, +geen revert-cascade — die machinerie is in T-1005…T-1007 uit `scrum4me-mcp` +verwijderd. Zie [auto-pr-flow.md](./auto-pr-flow.md) voor de gedeprecieerde +oude keten. + +## Wanneer naar GitHub: de promote-Action + +GitHub krijgt code via een **handmatig getriggerde Forgejo Action** in +dezelfde repo: + +``` +Forgejo branch (klaar voor review) + │ workflow_dispatch → Promote to GitHub + ▼ +GitHub branch + PR + │ + ▼ +PR-review · Vercel-preview · merge +``` + +Triggeren: Forgejo-UI → *Actions → Promote to GitHub → Run workflow* — kies de +branch (en of de GitHub-PR meteen geopend moet worden). + +De Action checkt de branch uit, pusht 'm naar GitHub via een PAT +(Forgejo-secret `GH_PROMOTE_TOKEN`) en opent (optioneel) de PR via de +GitHub-API. Daarna draait Vercel een preview-build op die PR — dat is het +enige moment waarop Vercel-build-tijd verbruikt wordt. + +## Main-sync terug + +Wanneer een GitHub-PR op `main` gemerged is, moet Forgejo's `main` daarmee in +lijn blijven zodat de volgende worker-branch op een actuele basis afsplitst. +Een **sync-back Forgejo Action** (`.forgejo/workflows/sync-main.yml`) doet +dit periodiek (cron) en op handmatige trigger. + +## Wat hierdoor verandert in de docs + +- `git push -u origin <branch>` blijft de finale stap van een worker-batch, + maar `origin` is nu **Forgejo**, niet GitHub. +- `gh pr create` komt niet meer voor in de worker-flow. +- "1 batch = 1 Vercel-deploy" geldt nog steeds, maar het moment van die + preview-deploy verschuift naar de **promote-trigger**, niet de worker-push. +- Auto-PR / auto-merge / mark-ready / revert-cascade zijn weg — wat handmatig + gebeurt: PR-review, merge, eventuele revert (allemaal op GitHub). + +## Verwante runbooks + +- [`branch-and-commit.md`](./branch-and-commit.md) — agent-batch-flow regels +- [`auto-pr-flow.md`](./auto-pr-flow.md) — historisch (gedeprecieerd) +- [`deploy-control.md`](./deploy-control.md) — Vercel-deploy-labels op GitHub-PR's From a3303a605b51ce44a412446675f7131c0219d95f Mon Sep 17 00:00:00 2001 From: Scrum4Me Agent <30029041+madhura68@users.noreply.github.com> Date: Fri, 15 May 2026 19:49:15 +0200 Subject: [PATCH 2/4] fix(ideas): consistent desc orderBy for userQuestions in timeline page userQuestions waren als enige `asc` opgehaald terwijl logs en questions `desc` gebruiken; het timeline-component sorteert toch client-side `desc`, maar consistentie voorkomt verwarring bij toekomstige wijzigingen. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --- app/(app)/ideas/[id]/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/(app)/ideas/[id]/page.tsx b/app/(app)/ideas/[id]/page.tsx index 80d946c..dcf7543 100644 --- a/app/(app)/ideas/[id]/page.tsx +++ b/app/(app)/ideas/[id]/page.tsx @@ -89,7 +89,7 @@ export default async function IdeaDetailPage({ params, searchParams }: PageProps const userQuestionsRaw = await prisma.userQuestion.findMany({ where: { idea_id: id }, - orderBy: { created_at: 'asc' }, + orderBy: { created_at: 'desc' }, take: 100, select: { id: true, question: true, answer: true, status: true, created_at: true }, }) From 22781365e6ff7b652c6a81a5d804621b0e4ce6af Mon Sep 17 00:00:00 2001 From: Scrum4Me Agent <30029041+madhura68@users.noreply.github.com> Date: Fri, 15 May 2026 19:57:01 +0200 Subject: [PATCH 3/4] feat(timeline): sticky chat-input bovenaan timeline-sectie MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verplaats UserChatInput naar boven in IdeaTimeline en geef de wrapper sticky top-0 z-10 bg-background border-b border-border — input blijft zichtbaar terwijl de timeline-items eronder doorscrollt. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --- components/ideas/idea-timeline.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/components/ideas/idea-timeline.tsx b/components/ideas/idea-timeline.tsx index b81eb42..5e17b94 100644 --- a/components/ideas/idea-timeline.tsx +++ b/components/ideas/idea-timeline.tsx @@ -128,7 +128,13 @@ export function IdeaTimeline({ const showChatInput = planMd !== null return ( - <div className="space-y-4" {...debugProps('idea-timeline', 'IdeaTimeline', 'components/ideas/idea-timeline.tsx')}> + <div {...debugProps('idea-timeline', 'IdeaTimeline', 'components/ideas/idea-timeline.tsx')}> + {showChatInput && ( + <div className="sticky top-0 z-10 bg-background border-b border-border pb-3 mb-4" data-debug-id="idea-timeline__chat-input"> + <UserChatInput ideaId={ideaId} isDemo={isDemo} /> + </div> + )} + <div className="space-y-4"> {merged.length === 0 ? ( <p className="text-sm text-muted-foreground py-8 text-center italic"> Nog geen activiteit op dit idee. @@ -252,8 +258,7 @@ export function IdeaTimeline({ })} </ol> )} - - {showChatInput && <UserChatInput ideaId={ideaId} isDemo={isDemo} />} + </div> </div> ) } From c9d4122b3a5b635baf2171ba1168ed6448afc9a5 Mon Sep 17 00:00:00 2001 From: Scrum4Me Agent <30029041+madhura68@users.noreply.github.com> Date: Fri, 15 May 2026 20:08:42 +0200 Subject: [PATCH 4/4] feat(timeline): reverse-chronologische volgorde + unit-test merge/sort Extraheert mergeTimelineItems uit IdeaTimeline als exporteerbare functie en voegt een vitest-test toe die de nieuwste-boven-sortering verifieert. revalidatePath in user-questions action was al aanwezig. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --- .../components/idea-timeline-merge.test.ts | 44 +++++++++++++++++++ components/ideas/idea-timeline.tsx | 35 ++++++++------- 2 files changed, 62 insertions(+), 17 deletions(-) create mode 100644 __tests__/components/idea-timeline-merge.test.ts diff --git a/__tests__/components/idea-timeline-merge.test.ts b/__tests__/components/idea-timeline-merge.test.ts new file mode 100644 index 0000000..4af127d --- /dev/null +++ b/__tests__/components/idea-timeline-merge.test.ts @@ -0,0 +1,44 @@ +import { describe, it, expect } from 'vitest' +import { mergeTimelineItems } from '@/components/ideas/idea-timeline' + +describe('mergeTimelineItems', () => { + it('sorteert reverse-chronologisch: nieuwste entry staat eerst', () => { + const logs = [ + { id: 'l1', type: 'NOTE', content: 'oud', metadata: null, created_at: '2024-01-01T10:00:00.000Z' }, + ] + const questions = [ + { + id: 'q1', + question: 'Vraag?', + options: null, + status: 'open' as const, + answer: null, + created_at: '2024-01-03T12:00:00.000Z', + expires_at: '2024-01-10T12:00:00.000Z', + }, + ] + const userQuestions = [ + { + id: 'uq1', + question: 'Mijn vraag', + answer: null, + status: 'pending' as const, + created_at: '2024-01-02T08:00:00.000Z', + }, + ] + + const result = mergeTimelineItems(logs, questions, userQuestions) + + expect(result).toHaveLength(3) + expect(result[0].created_at).toBe('2024-01-03T12:00:00.000Z') + expect(result[0].kind).toBe('question') + expect(result[1].created_at).toBe('2024-01-02T08:00:00.000Z') + expect(result[1].kind).toBe('user_question') + expect(result[2].created_at).toBe('2024-01-01T10:00:00.000Z') + expect(result[2].kind).toBe('log') + }) + + it('geeft lege lijst terug bij geen input', () => { + expect(mergeTimelineItems([], [], [])).toEqual([]) + }) +}) diff --git a/components/ideas/idea-timeline.tsx b/components/ideas/idea-timeline.tsx index 5e17b94..053289c 100644 --- a/components/ideas/idea-timeline.tsx +++ b/components/ideas/idea-timeline.tsx @@ -99,6 +99,23 @@ const USER_QUESTION_STATUS_LABEL: Record<TimelineUserQuestion['status'], string> answered: 'Beantwoord', } +export type TimelineEntry = + | { kind: 'log'; created_at: string; data: TimelineLog } + | { kind: 'question'; created_at: string; data: TimelineQuestion } + | { kind: 'user_question'; created_at: string; data: TimelineUserQuestion } + +export function mergeTimelineItems( + logs: TimelineLog[], + questions: TimelineQuestion[], + userQuestions: TimelineUserQuestion[], +): TimelineEntry[] { + return [ + ...logs.map((l) => ({ kind: 'log' as const, created_at: l.created_at, data: l })), + ...questions.map((q) => ({ kind: 'question' as const, created_at: q.created_at, data: q })), + ...userQuestions.map((uq) => ({ kind: 'user_question' as const, created_at: uq.created_at, data: uq })), + ].sort((a, b) => (a.created_at < b.created_at ? 1 : -1)) +} + export function IdeaTimeline({ logs, questions, @@ -107,23 +124,7 @@ export function IdeaTimeline({ ideaId, isDemo = false, }: Props) { - const merged = [ - ...logs.map((l) => ({ - kind: 'log' as const, - created_at: l.created_at, - data: l, - })), - ...questions.map((q) => ({ - kind: 'question' as const, - created_at: q.created_at, - data: q, - })), - ...userQuestions.map((uq) => ({ - kind: 'user_question' as const, - created_at: uq.created_at, - data: uq, - })), - ].sort((a, b) => (a.created_at < b.created_at ? 1 : -1)) + const merged = mergeTimelineItems(logs, questions, userQuestions) const showChatInput = planMd !== null