# Diffline — Product Requirements (v1.8)

This product is a pull-request review tool for the halden/billing-service repository. Reviewers sign in, find a pull request in a filterable list, read its commits and unified or split diffs, discuss individual lines in threads, submit reviews and merge once every merge requirement is met.

**Problem.** Reviewers need to find the right change, read it line by line, track which conversations are still open and know exactly what blocks a merge, without merging something that has failing checks, missing approvals or unresolved discussion.

## Users
- **Reviewer** — Read diffs, comment on lines, resolve threads and approve or request changes.
- **Author** — See what blocks the pull request and merge it once everything is satisfied.
- **Phone or tablet user** — Triage and review from a small screen without sideways scrolling.

## Goals
- The merge gate states every unmet requirement and never allows a merge while one is unmet.
- Counts (tabs, files, additions, deletions, resolved conversations) always equal what is on screen.
- Diffs read correctly: each deleted line sits beside the line that replaces it in split view.

## Sign-in and sign-out (AUTH)

Session access to the pull request list.

- **FR-AUTH-01** (Must) A signed-in user lands on the "Pull requests" page for halden/billing-service on the Open tab.
  - The sign-in page is headed "Sign in to" followed by the product name, with Username, Password and a "Sign in" button
  - After sign-in the tab strip reads Open 8, Merged 3, Closed 2, All 13
- **FR-AUTH-02** (Should) Failed sign-ins are rejected with a specific message and the form stays.
  - Empty username shows "Enter your username"
  - Wrong password shows "Invalid credentials" and no list is shown
- **FR-AUTH-03** (Could) Sign out ends the session and returns to the sign-in page; Back does not reveal any pull request.
  - Sign out is in the left rail, or in the bottom bar on a phone

## Pull request list (LST)

Tabs, filters, search and sort over the repository’s pull requests.

- **FR-LST-01** (Must) The Open tab lists the 8 open pull requests newest first with correct title, labels, author, date, review and check chips, comment count and +/- totals.
  - Order: #421, #420, #418, #417, #415, #414, #412, #409
  - #421 is "WIP: audit log CSV export" with a Draft pill, label feature, "#421 opened Oct 18, 2026 by Ines Carvalho", "1 pending", 0 comments, "+5 -0"
  - #412 "Add idempotency keys to POST /charges": labels feature and security, "4 passed", 4 comments, "+75 -1"
  - #409 "Fix rounding in partial and prorated refunds": label bug, "1 failing", 3 comments, "+12 -4"
  - #415 "Cache currency rates for 5 minutes": "3 passed", 1 comment, "+7 -0"
- **FR-LST-02** (Should) Merged, Closed and All tabs list only pull requests in that state, each with a state pill.
  - Merged (3): #411, #405, #403
  - Closed (2): #407 and #398
  - All shows 13, first #421, last #398
- **FR-LST-03** (Should) Label and author filters narrow the list and the heading count follows.
  - Label "security": 2 pull requests, #420 and #412
  - Author "Bao Nguyen": 2 pull requests, #415 and #409
- **FR-LST-04** (Should) Search matches title text; an empty result shows a way back.
  - "cache" returns only #415
  - No match shows "No pull requests match these filters", "0 pull requests" and a "Clear filters" button that restores the 8 open pull requests
- **FR-LST-05** (Could) "Awaiting my review" lists open pull requests where the signed-in user is a pending reviewer; the sort control can reverse the order.
  - "Awaiting my review": #414 and #412
  - "Oldest": #409, #412, #414, #415, #417, #418, #420, #421

## Pull request page (PRD)

Conversation, Commits and Files changed tabs with a sidebar of reviewers, checks and conversations.

- **FR-PRD-01** (Should) The page header and sidebar show the seeded author, branches, reviewers, checks and conversation summary.
  - #412: Open pill, "Tomasz Kowalski wants to merge tomasz/idempotency-keys into main", labels feature and security
  - Tabs: Conversation 4, Commits 4, Files changed 4 with "+75 -1"
  - Reviewers: Amira Hassan "approved"; Ines Carvalho and Bao Nguyen "review requested"
  - Checks: build, unit-tests and lint "Passed · required"; e2e-smoke "Passed"
  - Conversations: "1 of 2 resolved"
- **FR-PRD-02** (Could) The Commits tab lists the pull request’s commits oldest first with hash, message and author.
  - #412: a13f7c2 "Add idempotency helpers", 5be09d1 "Wire idempotency into POST /charges", f20c6ae "Add idempotency_keys migration", 62d8b07 "Cover replay and conflict in tests", all by Tomasz Kowalski
- **FR-PRD-03** (Could) A merged pull request shows who merged it and how, and offers no merge or close action.
  - #411 shows "Merged commit 4be71c9 into main by ahassan using squash and merge on Oct 12, 2026."

## Diff reading (DIF)

Files changed tab with file tree, unified and split layouts and viewed marks.

- **FR-DIF-01** (Must) Files changed lists every changed file with its own +/- counts and starts with nothing viewed.
  - #412: "0 of 4 files viewed", Unified selected
  - idempotency.ts +37 -0; charges.ts +15 -1; 0042_idempotency_keys.sql +9 -0; charges.test.ts +14 -0
  - The first file section reads src/payments/idempotency.ts, "added", "+37 -0" with an unchecked Viewed box
- **FR-DIF-02** (Must) Addition and deletion totals count every added and deleted line, in the list row, the tab and each file.
  - #412 shows "+75 -1" in the list and tab, and charges.ts "+15 -1"
  - src/payments/refunds.ts on #409 reads "+3 -3"
- **FR-DIF-03** (Must) In split view each deleted line sits on the same row as the added line that replaces it, and the chosen layout persists across reloads.
  - refunds.ts: left row 9 "const unused = (daysInPeriod - daysUsed) / daysInPeriod;" beside right row 9 "const unusedDays = daysInPeriod - daysUsed;"
  - Left row 10 "return Math.round(total * unused);" beside right row 10 "return Math.floor((total * unusedDays) / daysInPeriod);"
  - Unified view lists the deleted old lines 9 and 10, then the added new lines 9 and 10
- **FR-DIF-04** (Should) Ticking Viewed folds the file, advances the "N of M files viewed" counter and is remembered after reload.
  - Folded file reads "Marked as viewed — the diff is hidden." with a "Show diff" button; Viewed stays ticked when the diff is shown again
  - After viewing two files and reloading, "2 of 4 files viewed" is kept

## Inline conversations (THR)

Comment on a line, then resolve and unresolve the thread.

- **FR-THR-01** (Must) Clicking a line number opens a composer; adding a comment creates an unresolved conversation and updates the counts.
  - Composer reads "Comment on line 2" with Cancel and "Add comment"
  - An empty comment shows "Comment cannot be empty" and the composer stays
  - On success a toast "Comment added" appears; the Conversation tab reads 5 and the sidebar reads "1 of 3 resolved" listing idempotency.ts:2 as Unresolved
- **FR-THR-02** (Must) Resolving and unresolving a thread updates the sidebar "N of M resolved" summary and the merge gate line in both directions.
  - Resolving charges.ts line 34 on #412 shows toast "Conversation resolved", summary "2 of 2 resolved" and gate line "All conversations resolved"
  - Unresolving idempotency.ts line 4 shows toast "Conversation reopened"; the resolved count drops by one (from "1 of 2 resolved" to "0 of 2 resolved" with gate "2 unresolved conversations")
- **FR-THR-03** (Could) A resolved thread is collapsed to a one-line summary that expands on demand and offers "Unresolve conversation".
  - idempotency.ts line 4 shows a Resolved pill and "Show resolved conversation (2 comments)"
  - Expanded it shows Amira Hassan "Is 24 hours enough for clients that retry from a queue?" and Tomasz Kowalski "Matches the payment provider window. I can make it configurable in a follow-up."

## Reviews (REV)

Approve or request changes from the Review changes popover.

- **FR-REV-01** (Must) Approving adds the reviewer as "approved", increments the required-approvals count and refreshes the merge gate.
  - On #412 approving moves the Merge box from "1 of 2 required approvals" to "2 of 2 required approvals" and lists Ines Carvalho as "approved"
- **FR-REV-02** (Should) Requesting changes requires a summary; without one nothing changes.
  - "Add a comment explaining the requested changes" appears and the popover stays open
  - Reviewer states are unchanged afterwards
- **FR-REV-03** (Must) The author of a pull request cannot approve it.
  - The popover shows "You can't approve your own pull request"
  - The reviewer list and the approval count do not change

## Merge gate and merge (MRG)

A pull request merges only when every requirement is met.

- **FR-MRG-01** (Must) The Merge box lists five requirements: ready for review, required checks passed, 2 required approvals, no changes requested, all conversations resolved. The button is "Merge pull request" only when all are met, otherwise a disabled "Merge blocked".
  - #412 initially: "1 of 2 required approvals" and "1 unresolved conversation" not met, others met, button disabled
- **FR-MRG-02** (Must) A failing required check blocks the merge regardless of approvals.
  - With unit-tests "Failed · required", the requirement reads "1 required check failing" as not met and the button is a disabled "Merge blocked"
- **FR-MRG-03** (Should) Every unmet requirement is stated, including draft, pending check and review state.
  - #421: "This pull request is still a draft", "1 required check pending", "0 of 2 required approvals" not met; "No changes requested" and "All conversations resolved" met
  - #415: 3 checks passed (met); "0 of 2 required approvals", "Changes requested by smoreau" and "1 unresolved conversation" not met; Sophie Moreau is "requested changes"
- **FR-MRG-04** (Must) Merging asks for confirmation, then marks the pull request Merged with the chosen method and moves it between tab counts.
  - Methods include "Create a merge commit" (default) and "Squash and merge"; the squash title reads "Add idempotency keys to POST /charges (#412)" with counter "44/72"
  - Dialog "Merge pull request #412?" with Confirm merge; toast "Pull request merged"
  - Card reads "Merged commit <hash> into main by you using squash and merge on Oct 19, 2026."
  - After merging #412 the tabs read Open 7, Merged 4, Closed 2, All 13
- **FR-MRG-05** (Must) A pull request with no blockers merges with the default method and title.
  - #418: all five requirements met, "2 of 2 required approvals", method "Create a merge commit", Commit title "Docs: describe the webhook retry policy (#418)"
  - After merging, the card reads "...using create a merge commit on Oct 19, 2026." and the merge button is gone
- **FR-MRG-06** (Should) A pull request can be closed (after confirmation) and reopened; a draft must be marked ready before it can merge.
  - Dialog "Close pull request #418?" with Cancel; toast "Pull request closed"; pill Closed; merge box "This pull request is closed. Reopen it to continue the review."; timeline "closed this pull request"
  - Reopen shows toast "Pull request reopened", pill Open and an enabled "Merge pull request"
  - "Ready for review" replaces the Draft pill with Open and adds timeline "marked this pull request as ready for review"

## Keyboard shortcuts (KEY)

Review the files without a mouse.

- **FR-KEY-01** (Should) "?" opens a "Keyboard shortcuts" dialog listing j / k, v, s, /, ?, Esc and Ctrl+Enter; Esc closes it and "/" focuses search.
- **FR-KEY-02** (Should) On Files changed, j and k move the current file, v marks it viewed and s toggles split view.
  - j makes charges.ts current; v gives "1 of 4 files viewed" and folds it; s switches to Split; k returns to idempotency.ts

## Non-functional requirements

- **NFR-01** (Should) On a 390 px phone the list stacks, navigation moves to a bottom bar (Pull requests, Requests, Shortcuts, Sign out) and no page scrolls sideways.
  - 16 px side margins
  - On the pull request page the sidebar sections (Reviewers, Checks, Conversations, Labels) follow the conversation and the merge form is one full-width column
- **NFR-02** (Could) On a 768 px tablet the file list sits above the diffs and wide code scrolls inside its file card, not the page.

## Glossary and rules

- **Merge gate** — The set of five requirements that must all be met before a pull request can merge.
- **Required check** — A CI check marked "required"; it must pass for the gate to open.
- **Viewed** — A per-file mark that folds the diff and counts toward "N of M files viewed".

## Out of scope

- Real source control
- Notifications and email
- Branch protection settings
