BBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr view - #17

Merged
b2l merged 3 commits into
mainfrom
b2lpowa/bbc2-18-approve-unapprove-and-request-changes
Apr 15, 2026
Merged

BBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr view#17
b2l merged 3 commits into
mainfrom
b2lpowa/bbc2-18-approve-unapprove-and-request-changes

Conversation

@b2l

@b2lb2l commented Apr 15, 2026

Copy link
Copy Markdown
Owner

Summary

Four flat subcommands for recording your own review state on a PR. Each defaults to the open PR for the current branch when no id is given.

  • bb pr approve [<id>]
  • bb pr unapprove [<id>]
  • bb pr request-changes [<id>]
  • bb pr unrequest-changes [<id>]

Approve and request-changes are independent states in Bitbucket — a reviewer can carry both simultaneously across users; the same user can carry one or the other (toggling).

Naming choice — call it out

The ticket left the naming of the request-changes inverse open. I went with unrequest-changes for symmetry with unapprove (consistent un- prefix). Ugly, but self-evident and matches what's already there. If you'd prefer e.g. withdraw-changes or a flag-based bb pr request-changes --withdraw, easy to rename — say so before merge.

Idempotency caveat

The ticket AC says re-running an action on an already-matching state shouldn't error. The Bitbucket spec doesn't document what re-approving / re-requesting actually returns, so I haven't coded a swallow. Smoke-test will reveal the actual behavior:

  • If re-approve returns 200 → nothing to do, idempotent for free.
  • If it returns 4xx → I'll add a backend-level swallow in a follow-up commit.

Same for the DELETE-on-never-matching cases.

The one documented error path that IS handled cleanly: DELETE /approve returns 400 when the PR is already merged. Test covers it.

Implementation notes

  • Backend has 4 thin functions sharing two helpers (postParticipantAction / deleteParticipantAction) — keeps the per-action code minimal while typing the discriminated path correctly.
  • Command layer lives in one review.ts file with a shared runner; the 4 exported run functions just bind the action and the success message. 4 separate files would have been 90% duplication.

Test plan

  • On a real PR you didn't author: bun src/index.ts pr approve <id> — UI shows your approval. Run again — confirm whether it errors or no-ops (this is the smoke-test gating idempotency).
  • bun src/index.ts pr unapprove <id> — UI shows approval removed.
  • bun src/index.ts pr request-changes <id> — UI shows changes-requested.
  • bun src/index.ts pr unrequest-changes <id> — UI shows it cleared.
  • On a merged PR: bun src/index.ts pr unapprove <id> should fail with a clean message (not a stack trace).
  • No-id form: from a branch with an open PR, bun src/index.ts pr approve (no args) auto-detects.
  • Tests: bun test (151 passing, 6 new).
  • Lint: bun run lint.

Out of scope (per ticket)

  • Unified bb pr review command (could come later if the flat commands feel redundant).
  • Listing who approved / requested changes — already covered by bb pr view.

b2l added 3 commits April 15, 2026 12:00
Four flat commands, each defaulting to the current branch's PR when
no id is given:
- bb pr approve / bb pr unapprove
- bb pr request-changes / bb pr unrequest-changes
Approve and request-changes are independent review states in
Bitbucket — a reviewer can have an approval AND changes-requested
from different users on the same PR simultaneously. The four
commands wrap symmetric POST/DELETE pairs on /approve and
/request-changes.
Idempotency note: the spec doesn't document what re-approving an
already-approved PR returns. We let any non-2xx propagate. The
documented edge case — DELETE /approve returns 400 if the PR is
already merged — surfaces as a clean PullRequestError; the command
layer prints the message rather than a raw HTTP error.
See docs/bb-notes.md → Approve / Unapprove / Request-changes for
the endpoint details.
Two changes to make 'I approved it, now where did it go?' visible:
- Backend PullRequestDetail now carries participants[] alongside
reviewers[]. reviewers[] stays strictly role=REVIEWER (formal
reviewers, as before). participants[] covers role=PARTICIPANT —
including ad-hoc approvers on PRs with no assigned reviewers
(the Snyk auto-PR case). Pure commenters show up with
state=pending.
- bb pr view gets a single APPROVALS entry on the top block with
a one-line summary: 'N approved, M changes requested' aggregating
both arrays. Minimal addition; the larger REVIEWERS vs
PARTICIPANTS section split is deferred to its own ticket along
with the full pr view redesign.
Replace the four flat commands (approve / unapprove / request-changes
/ unrequest-changes) with a single bb pr review taking mutually-
exclusive flags:
-a, --approve
-r, --request-changes
-w, --withdraw
Matches gh's mental model of a single 'current review state' rather
than Bitbucket's two-independent-toggles API. --approve and
--request-changes implicitly clear the inverse state so the reviewer
can flip cleanly; --withdraw clears whichever state is set.
Backend functions unchanged — they remain the primitives the command
layer composes. The command layer's secondary cleanup calls are
best-effort (swallow PullRequestError) so a primary-action success
isn't masked by an expected 'not currently in that state' failure on
the cleanup DELETE.
@b2lb2l changed the title BBC2-18 add bb pr approve / unapprove / request-changes / unrequest-changesBBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr viewApr 15, 2026
@b2l
b2l merged commit dd65274 into mainApr 15, 2026
@b2l
b2l deleted the b2lpowa/bbc2-18-approve-unapprove-and-request-changes branch April 15, 2026 13:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@b2l
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

BBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr view - #17

Merged
b2l merged 3 commits into
mainfrom
b2lpowa/bbc2-18-approve-unapprove-and-request-changes
Apr 15, 2026
Merged

BBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr view#17
b2l merged 3 commits into
mainfrom
b2lpowa/bbc2-18-approve-unapprove-and-request-changes

Conversation

@b2l

@b2lb2l commented Apr 15, 2026

Copy link
Copy Markdown
Owner

Summary

Four flat subcommands for recording your own review state on a PR. Each defaults to the open PR for the current branch when no id is given.

  • bb pr approve [<id>]
  • bb pr unapprove [<id>]
  • bb pr request-changes [<id>]
  • bb pr unrequest-changes [<id>]

Approve and request-changes are independent states in Bitbucket — a reviewer can carry both simultaneously across users; the same user can carry one or the other (toggling).

Naming choice — call it out

The ticket left the naming of the request-changes inverse open. I went with unrequest-changes for symmetry with unapprove (consistent un- prefix). Ugly, but self-evident and matches what's already there. If you'd prefer e.g. withdraw-changes or a flag-based bb pr request-changes --withdraw, easy to rename — say so before merge.

Idempotency caveat

The ticket AC says re-running an action on an already-matching state shouldn't error. The Bitbucket spec doesn't document what re-approving / re-requesting actually returns, so I haven't coded a swallow. Smoke-test will reveal the actual behavior:

  • If re-approve returns 200 → nothing to do, idempotent for free.
  • If it returns 4xx → I'll add a backend-level swallow in a follow-up commit.

Same for the DELETE-on-never-matching cases.

The one documented error path that IS handled cleanly: DELETE /approve returns 400 when the PR is already merged. Test covers it.

Implementation notes

  • Backend has 4 thin functions sharing two helpers (postParticipantAction / deleteParticipantAction) — keeps the per-action code minimal while typing the discriminated path correctly.
  • Command layer lives in one review.ts file with a shared runner; the 4 exported run functions just bind the action and the success message. 4 separate files would have been 90% duplication.

Test plan

  • On a real PR you didn't author: bun src/index.ts pr approve <id> — UI shows your approval. Run again — confirm whether it errors or no-ops (this is the smoke-test gating idempotency).
  • bun src/index.ts pr unapprove <id> — UI shows approval removed.
  • bun src/index.ts pr request-changes <id> — UI shows changes-requested.
  • bun src/index.ts pr unrequest-changes <id> — UI shows it cleared.
  • On a merged PR: bun src/index.ts pr unapprove <id> should fail with a clean message (not a stack trace).
  • No-id form: from a branch with an open PR, bun src/index.ts pr approve (no args) auto-detects.
  • Tests: bun test (151 passing, 6 new).
  • Lint: bun run lint.

Out of scope (per ticket)

  • Unified bb pr review command (could come later if the flat commands feel redundant).
  • Listing who approved / requested changes — already covered by bb pr view.

b2l added 3 commits April 15, 2026 12:00
Four flat commands, each defaulting to the current branch's PR when
no id is given:
- bb pr approve / bb pr unapprove
- bb pr request-changes / bb pr unrequest-changes
Approve and request-changes are independent review states in
Bitbucket — a reviewer can have an approval AND changes-requested
from different users on the same PR simultaneously. The four
commands wrap symmetric POST/DELETE pairs on /approve and
/request-changes.
Idempotency note: the spec doesn't document what re-approving an
already-approved PR returns. We let any non-2xx propagate. The
documented edge case — DELETE /approve returns 400 if the PR is
already merged — surfaces as a clean PullRequestError; the command
layer prints the message rather than a raw HTTP error.
See docs/bb-notes.md → Approve / Unapprove / Request-changes for
the endpoint details.
Two changes to make 'I approved it, now where did it go?' visible:
- Backend PullRequestDetail now carries participants[] alongside
reviewers[]. reviewers[] stays strictly role=REVIEWER (formal
reviewers, as before). participants[] covers role=PARTICIPANT —
including ad-hoc approvers on PRs with no assigned reviewers
(the Snyk auto-PR case). Pure commenters show up with
state=pending.
- bb pr view gets a single APPROVALS entry on the top block with
a one-line summary: 'N approved, M changes requested' aggregating
both arrays. Minimal addition; the larger REVIEWERS vs
PARTICIPANTS section split is deferred to its own ticket along
with the full pr view redesign.
Replace the four flat commands (approve / unapprove / request-changes
/ unrequest-changes) with a single bb pr review taking mutually-
exclusive flags:
-a, --approve
-r, --request-changes
-w, --withdraw
Matches gh's mental model of a single 'current review state' rather
than Bitbucket's two-independent-toggles API. --approve and
--request-changes implicitly clear the inverse state so the reviewer
can flip cleanly; --withdraw clears whichever state is set.
Backend functions unchanged — they remain the primitives the command
layer composes. The command layer's secondary cleanup calls are
best-effort (swallow PullRequestError) so a primary-action success
isn't masked by an expected 'not currently in that state' failure on
the cleanup DELETE.
@b2lb2l changed the title BBC2-18 add bb pr approve / unapprove / request-changes / unrequest-changesBBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr viewApr 15, 2026
@b2l
b2l merged commit dd65274 into mainApr 15, 2026
@b2l
b2l deleted the b2lpowa/bbc2-18-approve-unapprove-and-request-changes branch April 15, 2026 13:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@b2l
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

BBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr view - #17

Merged
b2l merged 3 commits into
mainfrom
b2lpowa/bbc2-18-approve-unapprove-and-request-changes
Apr 15, 2026
Merged

BBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr view#17
b2l merged 3 commits into
mainfrom
b2lpowa/bbc2-18-approve-unapprove-and-request-changes

Conversation

@b2l

@b2lb2l commented Apr 15, 2026

Copy link
Copy Markdown
Owner

Summary

Four flat subcommands for recording your own review state on a PR. Each defaults to the open PR for the current branch when no id is given.

  • bb pr approve [<id>]
  • bb pr unapprove [<id>]
  • bb pr request-changes [<id>]
  • bb pr unrequest-changes [<id>]

Approve and request-changes are independent states in Bitbucket — a reviewer can carry both simultaneously across users; the same user can carry one or the other (toggling).

Naming choice — call it out

The ticket left the naming of the request-changes inverse open. I went with unrequest-changes for symmetry with unapprove (consistent un- prefix). Ugly, but self-evident and matches what's already there. If you'd prefer e.g. withdraw-changes or a flag-based bb pr request-changes --withdraw, easy to rename — say so before merge.

Idempotency caveat

The ticket AC says re-running an action on an already-matching state shouldn't error. The Bitbucket spec doesn't document what re-approving / re-requesting actually returns, so I haven't coded a swallow. Smoke-test will reveal the actual behavior:

  • If re-approve returns 200 → nothing to do, idempotent for free.
  • If it returns 4xx → I'll add a backend-level swallow in a follow-up commit.

Same for the DELETE-on-never-matching cases.

The one documented error path that IS handled cleanly: DELETE /approve returns 400 when the PR is already merged. Test covers it.

Implementation notes

  • Backend has 4 thin functions sharing two helpers (postParticipantAction / deleteParticipantAction) — keeps the per-action code minimal while typing the discriminated path correctly.
  • Command layer lives in one review.ts file with a shared runner; the 4 exported run functions just bind the action and the success message. 4 separate files would have been 90% duplication.

Test plan

  • On a real PR you didn't author: bun src/index.ts pr approve <id> — UI shows your approval. Run again — confirm whether it errors or no-ops (this is the smoke-test gating idempotency).
  • bun src/index.ts pr unapprove <id> — UI shows approval removed.
  • bun src/index.ts pr request-changes <id> — UI shows changes-requested.
  • bun src/index.ts pr unrequest-changes <id> — UI shows it cleared.
  • On a merged PR: bun src/index.ts pr unapprove <id> should fail with a clean message (not a stack trace).
  • No-id form: from a branch with an open PR, bun src/index.ts pr approve (no args) auto-detects.
  • Tests: bun test (151 passing, 6 new).
  • Lint: bun run lint.

Out of scope (per ticket)

  • Unified bb pr review command (could come later if the flat commands feel redundant).
  • Listing who approved / requested changes — already covered by bb pr view.

b2l added 3 commits April 15, 2026 12:00
Four flat commands, each defaulting to the current branch's PR when
no id is given:
- bb pr approve / bb pr unapprove
- bb pr request-changes / bb pr unrequest-changes
Approve and request-changes are independent review states in
Bitbucket — a reviewer can have an approval AND changes-requested
from different users on the same PR simultaneously. The four
commands wrap symmetric POST/DELETE pairs on /approve and
/request-changes.
Idempotency note: the spec doesn't document what re-approving an
already-approved PR returns. We let any non-2xx propagate. The
documented edge case — DELETE /approve returns 400 if the PR is
already merged — surfaces as a clean PullRequestError; the command
layer prints the message rather than a raw HTTP error.
See docs/bb-notes.md → Approve / Unapprove / Request-changes for
the endpoint details.
Two changes to make 'I approved it, now where did it go?' visible:
- Backend PullRequestDetail now carries participants[] alongside
reviewers[]. reviewers[] stays strictly role=REVIEWER (formal
reviewers, as before). participants[] covers role=PARTICIPANT —
including ad-hoc approvers on PRs with no assigned reviewers
(the Snyk auto-PR case). Pure commenters show up with
state=pending.
- bb pr view gets a single APPROVALS entry on the top block with
a one-line summary: 'N approved, M changes requested' aggregating
both arrays. Minimal addition; the larger REVIEWERS vs
PARTICIPANTS section split is deferred to its own ticket along
with the full pr view redesign.
Replace the four flat commands (approve / unapprove / request-changes
/ unrequest-changes) with a single bb pr review taking mutually-
exclusive flags:
-a, --approve
-r, --request-changes
-w, --withdraw
Matches gh's mental model of a single 'current review state' rather
than Bitbucket's two-independent-toggles API. --approve and
--request-changes implicitly clear the inverse state so the reviewer
can flip cleanly; --withdraw clears whichever state is set.
Backend functions unchanged — they remain the primitives the command
layer composes. The command layer's secondary cleanup calls are
best-effort (swallow PullRequestError) so a primary-action success
isn't masked by an expected 'not currently in that state' failure on
the cleanup DELETE.
@b2lb2l changed the title BBC2-18 add bb pr approve / unapprove / request-changes / unrequest-changesBBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr viewApr 15, 2026
@b2l
b2l merged commit dd65274 into mainApr 15, 2026
@b2l
b2l deleted the b2lpowa/bbc2-18-approve-unapprove-and-request-changes branch April 15, 2026 13:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@b2l
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

BBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr view - #17

Merged
b2l merged 3 commits into
mainfrom
b2lpowa/bbc2-18-approve-unapprove-and-request-changes
Apr 15, 2026
Merged

BBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr view#17
b2l merged 3 commits into
mainfrom
b2lpowa/bbc2-18-approve-unapprove-and-request-changes

Conversation

@b2l

@b2lb2l commented Apr 15, 2026

Copy link
Copy Markdown
Owner

Summary

Four flat subcommands for recording your own review state on a PR. Each defaults to the open PR for the current branch when no id is given.

  • bb pr approve [<id>]
  • bb pr unapprove [<id>]
  • bb pr request-changes [<id>]
  • bb pr unrequest-changes [<id>]

Approve and request-changes are independent states in Bitbucket — a reviewer can carry both simultaneously across users; the same user can carry one or the other (toggling).

Naming choice — call it out

The ticket left the naming of the request-changes inverse open. I went with unrequest-changes for symmetry with unapprove (consistent un- prefix). Ugly, but self-evident and matches what's already there. If you'd prefer e.g. withdraw-changes or a flag-based bb pr request-changes --withdraw, easy to rename — say so before merge.

Idempotency caveat

The ticket AC says re-running an action on an already-matching state shouldn't error. The Bitbucket spec doesn't document what re-approving / re-requesting actually returns, so I haven't coded a swallow. Smoke-test will reveal the actual behavior:

  • If re-approve returns 200 → nothing to do, idempotent for free.
  • If it returns 4xx → I'll add a backend-level swallow in a follow-up commit.

Same for the DELETE-on-never-matching cases.

The one documented error path that IS handled cleanly: DELETE /approve returns 400 when the PR is already merged. Test covers it.

Implementation notes

  • Backend has 4 thin functions sharing two helpers (postParticipantAction / deleteParticipantAction) — keeps the per-action code minimal while typing the discriminated path correctly.
  • Command layer lives in one review.ts file with a shared runner; the 4 exported run functions just bind the action and the success message. 4 separate files would have been 90% duplication.

Test plan

  • On a real PR you didn't author: bun src/index.ts pr approve <id> — UI shows your approval. Run again — confirm whether it errors or no-ops (this is the smoke-test gating idempotency).
  • bun src/index.ts pr unapprove <id> — UI shows approval removed.
  • bun src/index.ts pr request-changes <id> — UI shows changes-requested.
  • bun src/index.ts pr unrequest-changes <id> — UI shows it cleared.
  • On a merged PR: bun src/index.ts pr unapprove <id> should fail with a clean message (not a stack trace).
  • No-id form: from a branch with an open PR, bun src/index.ts pr approve (no args) auto-detects.
  • Tests: bun test (151 passing, 6 new).
  • Lint: bun run lint.

Out of scope (per ticket)

  • Unified bb pr review command (could come later if the flat commands feel redundant).
  • Listing who approved / requested changes — already covered by bb pr view.

b2l added 3 commits April 15, 2026 12:00
Four flat commands, each defaulting to the current branch's PR when
no id is given:
- bb pr approve / bb pr unapprove
- bb pr request-changes / bb pr unrequest-changes
Approve and request-changes are independent review states in
Bitbucket — a reviewer can have an approval AND changes-requested
from different users on the same PR simultaneously. The four
commands wrap symmetric POST/DELETE pairs on /approve and
/request-changes.
Idempotency note: the spec doesn't document what re-approving an
already-approved PR returns. We let any non-2xx propagate. The
documented edge case — DELETE /approve returns 400 if the PR is
already merged — surfaces as a clean PullRequestError; the command
layer prints the message rather than a raw HTTP error.
See docs/bb-notes.md → Approve / Unapprove / Request-changes for
the endpoint details.
Two changes to make 'I approved it, now where did it go?' visible:
- Backend PullRequestDetail now carries participants[] alongside
reviewers[]. reviewers[] stays strictly role=REVIEWER (formal
reviewers, as before). participants[] covers role=PARTICIPANT —
including ad-hoc approvers on PRs with no assigned reviewers
(the Snyk auto-PR case). Pure commenters show up with
state=pending.
- bb pr view gets a single APPROVALS entry on the top block with
a one-line summary: 'N approved, M changes requested' aggregating
both arrays. Minimal addition; the larger REVIEWERS vs
PARTICIPANTS section split is deferred to its own ticket along
with the full pr view redesign.
Replace the four flat commands (approve / unapprove / request-changes
/ unrequest-changes) with a single bb pr review taking mutually-
exclusive flags:
-a, --approve
-r, --request-changes
-w, --withdraw
Matches gh's mental model of a single 'current review state' rather
than Bitbucket's two-independent-toggles API. --approve and
--request-changes implicitly clear the inverse state so the reviewer
can flip cleanly; --withdraw clears whichever state is set.
Backend functions unchanged — they remain the primitives the command
layer composes. The command layer's secondary cleanup calls are
best-effort (swallow PullRequestError) so a primary-action success
isn't masked by an expected 'not currently in that state' failure on
the cleanup DELETE.
@b2lb2l changed the title BBC2-18 add bb pr approve / unapprove / request-changes / unrequest-changesBBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr viewApr 15, 2026
@b2l
b2l merged commit dd65274 into mainApr 15, 2026
@b2l
b2l deleted the b2lpowa/bbc2-18-approve-unapprove-and-request-changes branch April 15, 2026 13:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@b2l
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

BBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr view - #17

Merged
b2l merged 3 commits into
mainfrom
b2lpowa/bbc2-18-approve-unapprove-and-request-changes
Apr 15, 2026
Merged

BBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr view#17
b2l merged 3 commits into
mainfrom
b2lpowa/bbc2-18-approve-unapprove-and-request-changes

Conversation

@b2l

@b2lb2l commented Apr 15, 2026

Copy link
Copy Markdown
Owner

Summary

Four flat subcommands for recording your own review state on a PR. Each defaults to the open PR for the current branch when no id is given.

  • bb pr approve [<id>]
  • bb pr unapprove [<id>]
  • bb pr request-changes [<id>]
  • bb pr unrequest-changes [<id>]

Approve and request-changes are independent states in Bitbucket — a reviewer can carry both simultaneously across users; the same user can carry one or the other (toggling).

Naming choice — call it out

The ticket left the naming of the request-changes inverse open. I went with unrequest-changes for symmetry with unapprove (consistent un- prefix). Ugly, but self-evident and matches what's already there. If you'd prefer e.g. withdraw-changes or a flag-based bb pr request-changes --withdraw, easy to rename — say so before merge.

Idempotency caveat

The ticket AC says re-running an action on an already-matching state shouldn't error. The Bitbucket spec doesn't document what re-approving / re-requesting actually returns, so I haven't coded a swallow. Smoke-test will reveal the actual behavior:

  • If re-approve returns 200 → nothing to do, idempotent for free.
  • If it returns 4xx → I'll add a backend-level swallow in a follow-up commit.

Same for the DELETE-on-never-matching cases.

The one documented error path that IS handled cleanly: DELETE /approve returns 400 when the PR is already merged. Test covers it.

Implementation notes

  • Backend has 4 thin functions sharing two helpers (postParticipantAction / deleteParticipantAction) — keeps the per-action code minimal while typing the discriminated path correctly.
  • Command layer lives in one review.ts file with a shared runner; the 4 exported run functions just bind the action and the success message. 4 separate files would have been 90% duplication.

Test plan

  • On a real PR you didn't author: bun src/index.ts pr approve <id> — UI shows your approval. Run again — confirm whether it errors or no-ops (this is the smoke-test gating idempotency).
  • bun src/index.ts pr unapprove <id> — UI shows approval removed.
  • bun src/index.ts pr request-changes <id> — UI shows changes-requested.
  • bun src/index.ts pr unrequest-changes <id> — UI shows it cleared.
  • On a merged PR: bun src/index.ts pr unapprove <id> should fail with a clean message (not a stack trace).
  • No-id form: from a branch with an open PR, bun src/index.ts pr approve (no args) auto-detects.
  • Tests: bun test (151 passing, 6 new).
  • Lint: bun run lint.

Out of scope (per ticket)

  • Unified bb pr review command (could come later if the flat commands feel redundant).
  • Listing who approved / requested changes — already covered by bb pr view.

b2l added 3 commits April 15, 2026 12:00
Four flat commands, each defaulting to the current branch's PR when
no id is given:
- bb pr approve / bb pr unapprove
- bb pr request-changes / bb pr unrequest-changes
Approve and request-changes are independent review states in
Bitbucket — a reviewer can have an approval AND changes-requested
from different users on the same PR simultaneously. The four
commands wrap symmetric POST/DELETE pairs on /approve and
/request-changes.
Idempotency note: the spec doesn't document what re-approving an
already-approved PR returns. We let any non-2xx propagate. The
documented edge case — DELETE /approve returns 400 if the PR is
already merged — surfaces as a clean PullRequestError; the command
layer prints the message rather than a raw HTTP error.
See docs/bb-notes.md → Approve / Unapprove / Request-changes for
the endpoint details.
Two changes to make 'I approved it, now where did it go?' visible:
- Backend PullRequestDetail now carries participants[] alongside
reviewers[]. reviewers[] stays strictly role=REVIEWER (formal
reviewers, as before). participants[] covers role=PARTICIPANT —
including ad-hoc approvers on PRs with no assigned reviewers
(the Snyk auto-PR case). Pure commenters show up with
state=pending.
- bb pr view gets a single APPROVALS entry on the top block with
a one-line summary: 'N approved, M changes requested' aggregating
both arrays. Minimal addition; the larger REVIEWERS vs
PARTICIPANTS section split is deferred to its own ticket along
with the full pr view redesign.
Replace the four flat commands (approve / unapprove / request-changes
/ unrequest-changes) with a single bb pr review taking mutually-
exclusive flags:
-a, --approve
-r, --request-changes
-w, --withdraw
Matches gh's mental model of a single 'current review state' rather
than Bitbucket's two-independent-toggles API. --approve and
--request-changes implicitly clear the inverse state so the reviewer
can flip cleanly; --withdraw clears whichever state is set.
Backend functions unchanged — they remain the primitives the command
layer composes. The command layer's secondary cleanup calls are
best-effort (swallow PullRequestError) so a primary-action success
isn't masked by an expected 'not currently in that state' failure on
the cleanup DELETE.
@b2lb2l changed the title BBC2-18 add bb pr approve / unapprove / request-changes / unrequest-changesBBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr viewApr 15, 2026
@b2l
b2l merged commit dd65274 into mainApr 15, 2026
@b2l
b2l deleted the b2lpowa/bbc2-18-approve-unapprove-and-request-changes branch April 15, 2026 13:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@b2l
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

BBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr view - #17

Merged
b2l merged 3 commits into
mainfrom
b2lpowa/bbc2-18-approve-unapprove-and-request-changes
Apr 15, 2026
Merged

BBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr view#17
b2l merged 3 commits into
mainfrom
b2lpowa/bbc2-18-approve-unapprove-and-request-changes

Conversation

@b2l

@b2lb2l commented Apr 15, 2026

Copy link
Copy Markdown
Owner

Summary

Four flat subcommands for recording your own review state on a PR. Each defaults to the open PR for the current branch when no id is given.

  • bb pr approve [<id>]
  • bb pr unapprove [<id>]
  • bb pr request-changes [<id>]
  • bb pr unrequest-changes [<id>]

Approve and request-changes are independent states in Bitbucket — a reviewer can carry both simultaneously across users; the same user can carry one or the other (toggling).

Naming choice — call it out

The ticket left the naming of the request-changes inverse open. I went with unrequest-changes for symmetry with unapprove (consistent un- prefix). Ugly, but self-evident and matches what's already there. If you'd prefer e.g. withdraw-changes or a flag-based bb pr request-changes --withdraw, easy to rename — say so before merge.

Idempotency caveat

The ticket AC says re-running an action on an already-matching state shouldn't error. The Bitbucket spec doesn't document what re-approving / re-requesting actually returns, so I haven't coded a swallow. Smoke-test will reveal the actual behavior:

  • If re-approve returns 200 → nothing to do, idempotent for free.
  • If it returns 4xx → I'll add a backend-level swallow in a follow-up commit.

Same for the DELETE-on-never-matching cases.

The one documented error path that IS handled cleanly: DELETE /approve returns 400 when the PR is already merged. Test covers it.

Implementation notes

  • Backend has 4 thin functions sharing two helpers (postParticipantAction / deleteParticipantAction) — keeps the per-action code minimal while typing the discriminated path correctly.
  • Command layer lives in one review.ts file with a shared runner; the 4 exported run functions just bind the action and the success message. 4 separate files would have been 90% duplication.

Test plan

  • On a real PR you didn't author: bun src/index.ts pr approve <id> — UI shows your approval. Run again — confirm whether it errors or no-ops (this is the smoke-test gating idempotency).
  • bun src/index.ts pr unapprove <id> — UI shows approval removed.
  • bun src/index.ts pr request-changes <id> — UI shows changes-requested.
  • bun src/index.ts pr unrequest-changes <id> — UI shows it cleared.
  • On a merged PR: bun src/index.ts pr unapprove <id> should fail with a clean message (not a stack trace).
  • No-id form: from a branch with an open PR, bun src/index.ts pr approve (no args) auto-detects.
  • Tests: bun test (151 passing, 6 new).
  • Lint: bun run lint.

Out of scope (per ticket)

  • Unified bb pr review command (could come later if the flat commands feel redundant).
  • Listing who approved / requested changes — already covered by bb pr view.

b2l added 3 commits April 15, 2026 12:00
Four flat commands, each defaulting to the current branch's PR when
no id is given:
- bb pr approve / bb pr unapprove
- bb pr request-changes / bb pr unrequest-changes
Approve and request-changes are independent review states in
Bitbucket — a reviewer can have an approval AND changes-requested
from different users on the same PR simultaneously. The four
commands wrap symmetric POST/DELETE pairs on /approve and
/request-changes.
Idempotency note: the spec doesn't document what re-approving an
already-approved PR returns. We let any non-2xx propagate. The
documented edge case — DELETE /approve returns 400 if the PR is
already merged — surfaces as a clean PullRequestError; the command
layer prints the message rather than a raw HTTP error.
See docs/bb-notes.md → Approve / Unapprove / Request-changes for
the endpoint details.
Two changes to make 'I approved it, now where did it go?' visible:
- Backend PullRequestDetail now carries participants[] alongside
reviewers[]. reviewers[] stays strictly role=REVIEWER (formal
reviewers, as before). participants[] covers role=PARTICIPANT —
including ad-hoc approvers on PRs with no assigned reviewers
(the Snyk auto-PR case). Pure commenters show up with
state=pending.
- bb pr view gets a single APPROVALS entry on the top block with
a one-line summary: 'N approved, M changes requested' aggregating
both arrays. Minimal addition; the larger REVIEWERS vs
PARTICIPANTS section split is deferred to its own ticket along
with the full pr view redesign.
Replace the four flat commands (approve / unapprove / request-changes
/ unrequest-changes) with a single bb pr review taking mutually-
exclusive flags:
-a, --approve
-r, --request-changes
-w, --withdraw
Matches gh's mental model of a single 'current review state' rather
than Bitbucket's two-independent-toggles API. --approve and
--request-changes implicitly clear the inverse state so the reviewer
can flip cleanly; --withdraw clears whichever state is set.
Backend functions unchanged — they remain the primitives the command
layer composes. The command layer's secondary cleanup calls are
best-effort (swallow PullRequestError) so a primary-action success
isn't masked by an expected 'not currently in that state' failure on
the cleanup DELETE.
@b2lb2l changed the title BBC2-18 add bb pr approve / unapprove / request-changes / unrequest-changesBBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr viewApr 15, 2026
@b2l
b2l merged commit dd65274 into mainApr 15, 2026
@b2l
b2l deleted the b2lpowa/bbc2-18-approve-unapprove-and-request-changes branch April 15, 2026 13:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@b2l
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

BBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr view - #17

Merged
b2l merged 3 commits into
mainfrom
b2lpowa/bbc2-18-approve-unapprove-and-request-changes
Apr 15, 2026
Merged

BBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr view#17
b2l merged 3 commits into
mainfrom
b2lpowa/bbc2-18-approve-unapprove-and-request-changes

Conversation

@b2l

@b2lb2l commented Apr 15, 2026

Copy link
Copy Markdown
Owner

Summary

Four flat subcommands for recording your own review state on a PR. Each defaults to the open PR for the current branch when no id is given.

  • bb pr approve [<id>]
  • bb pr unapprove [<id>]
  • bb pr request-changes [<id>]
  • bb pr unrequest-changes [<id>]

Approve and request-changes are independent states in Bitbucket — a reviewer can carry both simultaneously across users; the same user can carry one or the other (toggling).

Naming choice — call it out

The ticket left the naming of the request-changes inverse open. I went with unrequest-changes for symmetry with unapprove (consistent un- prefix). Ugly, but self-evident and matches what's already there. If you'd prefer e.g. withdraw-changes or a flag-based bb pr request-changes --withdraw, easy to rename — say so before merge.

Idempotency caveat

The ticket AC says re-running an action on an already-matching state shouldn't error. The Bitbucket spec doesn't document what re-approving / re-requesting actually returns, so I haven't coded a swallow. Smoke-test will reveal the actual behavior:

  • If re-approve returns 200 → nothing to do, idempotent for free.
  • If it returns 4xx → I'll add a backend-level swallow in a follow-up commit.

Same for the DELETE-on-never-matching cases.

The one documented error path that IS handled cleanly: DELETE /approve returns 400 when the PR is already merged. Test covers it.

Implementation notes

  • Backend has 4 thin functions sharing two helpers (postParticipantAction / deleteParticipantAction) — keeps the per-action code minimal while typing the discriminated path correctly.
  • Command layer lives in one review.ts file with a shared runner; the 4 exported run functions just bind the action and the success message. 4 separate files would have been 90% duplication.

Test plan

  • On a real PR you didn't author: bun src/index.ts pr approve <id> — UI shows your approval. Run again — confirm whether it errors or no-ops (this is the smoke-test gating idempotency).
  • bun src/index.ts pr unapprove <id> — UI shows approval removed.
  • bun src/index.ts pr request-changes <id> — UI shows changes-requested.
  • bun src/index.ts pr unrequest-changes <id> — UI shows it cleared.
  • On a merged PR: bun src/index.ts pr unapprove <id> should fail with a clean message (not a stack trace).
  • No-id form: from a branch with an open PR, bun src/index.ts pr approve (no args) auto-detects.
  • Tests: bun test (151 passing, 6 new).
  • Lint: bun run lint.

Out of scope (per ticket)

  • Unified bb pr review command (could come later if the flat commands feel redundant).
  • Listing who approved / requested changes — already covered by bb pr view.

b2l added 3 commits April 15, 2026 12:00
Four flat commands, each defaulting to the current branch's PR when
no id is given:
- bb pr approve / bb pr unapprove
- bb pr request-changes / bb pr unrequest-changes
Approve and request-changes are independent review states in
Bitbucket — a reviewer can have an approval AND changes-requested
from different users on the same PR simultaneously. The four
commands wrap symmetric POST/DELETE pairs on /approve and
/request-changes.
Idempotency note: the spec doesn't document what re-approving an
already-approved PR returns. We let any non-2xx propagate. The
documented edge case — DELETE /approve returns 400 if the PR is
already merged — surfaces as a clean PullRequestError; the command
layer prints the message rather than a raw HTTP error.
See docs/bb-notes.md → Approve / Unapprove / Request-changes for
the endpoint details.
Two changes to make 'I approved it, now where did it go?' visible:
- Backend PullRequestDetail now carries participants[] alongside
reviewers[]. reviewers[] stays strictly role=REVIEWER (formal
reviewers, as before). participants[] covers role=PARTICIPANT —
including ad-hoc approvers on PRs with no assigned reviewers
(the Snyk auto-PR case). Pure commenters show up with
state=pending.
- bb pr view gets a single APPROVALS entry on the top block with
a one-line summary: 'N approved, M changes requested' aggregating
both arrays. Minimal addition; the larger REVIEWERS vs
PARTICIPANTS section split is deferred to its own ticket along
with the full pr view redesign.
Replace the four flat commands (approve / unapprove / request-changes
/ unrequest-changes) with a single bb pr review taking mutually-
exclusive flags:
-a, --approve
-r, --request-changes
-w, --withdraw
Matches gh's mental model of a single 'current review state' rather
than Bitbucket's two-independent-toggles API. --approve and
--request-changes implicitly clear the inverse state so the reviewer
can flip cleanly; --withdraw clears whichever state is set.
Backend functions unchanged — they remain the primitives the command
layer composes. The command layer's secondary cleanup calls are
best-effort (swallow PullRequestError) so a primary-action success
isn't masked by an expected 'not currently in that state' failure on
the cleanup DELETE.
@b2lb2l changed the title BBC2-18 add bb pr approve / unapprove / request-changes / unrequest-changesBBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr viewApr 15, 2026
@b2l
b2l merged commit dd65274 into mainApr 15, 2026
@b2l
b2l deleted the b2lpowa/bbc2-18-approve-unapprove-and-request-changes branch April 15, 2026 13:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@b2l
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

BBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr view - #17

Merged
b2l merged 3 commits into
mainfrom
b2lpowa/bbc2-18-approve-unapprove-and-request-changes
Apr 15, 2026
Merged

BBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr view#17
b2l merged 3 commits into
mainfrom
b2lpowa/bbc2-18-approve-unapprove-and-request-changes

Conversation

@b2l

@b2lb2l commented Apr 15, 2026

Copy link
Copy Markdown
Owner

Summary

Four flat subcommands for recording your own review state on a PR. Each defaults to the open PR for the current branch when no id is given.

  • bb pr approve [<id>]
  • bb pr unapprove [<id>]
  • bb pr request-changes [<id>]
  • bb pr unrequest-changes [<id>]

Approve and request-changes are independent states in Bitbucket — a reviewer can carry both simultaneously across users; the same user can carry one or the other (toggling).

Naming choice — call it out

The ticket left the naming of the request-changes inverse open. I went with unrequest-changes for symmetry with unapprove (consistent un- prefix). Ugly, but self-evident and matches what's already there. If you'd prefer e.g. withdraw-changes or a flag-based bb pr request-changes --withdraw, easy to rename — say so before merge.

Idempotency caveat

The ticket AC says re-running an action on an already-matching state shouldn't error. The Bitbucket spec doesn't document what re-approving / re-requesting actually returns, so I haven't coded a swallow. Smoke-test will reveal the actual behavior:

  • If re-approve returns 200 → nothing to do, idempotent for free.
  • If it returns 4xx → I'll add a backend-level swallow in a follow-up commit.

Same for the DELETE-on-never-matching cases.

The one documented error path that IS handled cleanly: DELETE /approve returns 400 when the PR is already merged. Test covers it.

Implementation notes

  • Backend has 4 thin functions sharing two helpers (postParticipantAction / deleteParticipantAction) — keeps the per-action code minimal while typing the discriminated path correctly.
  • Command layer lives in one review.ts file with a shared runner; the 4 exported run functions just bind the action and the success message. 4 separate files would have been 90% duplication.

Test plan

  • On a real PR you didn't author: bun src/index.ts pr approve <id> — UI shows your approval. Run again — confirm whether it errors or no-ops (this is the smoke-test gating idempotency).
  • bun src/index.ts pr unapprove <id> — UI shows approval removed.
  • bun src/index.ts pr request-changes <id> — UI shows changes-requested.
  • bun src/index.ts pr unrequest-changes <id> — UI shows it cleared.
  • On a merged PR: bun src/index.ts pr unapprove <id> should fail with a clean message (not a stack trace).
  • No-id form: from a branch with an open PR, bun src/index.ts pr approve (no args) auto-detects.
  • Tests: bun test (151 passing, 6 new).
  • Lint: bun run lint.

Out of scope (per ticket)

  • Unified bb pr review command (could come later if the flat commands feel redundant).
  • Listing who approved / requested changes — already covered by bb pr view.

b2l added 3 commits April 15, 2026 12:00
Four flat commands, each defaulting to the current branch's PR when
no id is given:
- bb pr approve / bb pr unapprove
- bb pr request-changes / bb pr unrequest-changes
Approve and request-changes are independent review states in
Bitbucket — a reviewer can have an approval AND changes-requested
from different users on the same PR simultaneously. The four
commands wrap symmetric POST/DELETE pairs on /approve and
/request-changes.
Idempotency note: the spec doesn't document what re-approving an
already-approved PR returns. We let any non-2xx propagate. The
documented edge case — DELETE /approve returns 400 if the PR is
already merged — surfaces as a clean PullRequestError; the command
layer prints the message rather than a raw HTTP error.
See docs/bb-notes.md → Approve / Unapprove / Request-changes for
the endpoint details.
Two changes to make 'I approved it, now where did it go?' visible:
- Backend PullRequestDetail now carries participants[] alongside
reviewers[]. reviewers[] stays strictly role=REVIEWER (formal
reviewers, as before). participants[] covers role=PARTICIPANT —
including ad-hoc approvers on PRs with no assigned reviewers
(the Snyk auto-PR case). Pure commenters show up with
state=pending.
- bb pr view gets a single APPROVALS entry on the top block with
a one-line summary: 'N approved, M changes requested' aggregating
both arrays. Minimal addition; the larger REVIEWERS vs
PARTICIPANTS section split is deferred to its own ticket along
with the full pr view redesign.
Replace the four flat commands (approve / unapprove / request-changes
/ unrequest-changes) with a single bb pr review taking mutually-
exclusive flags:
-a, --approve
-r, --request-changes
-w, --withdraw
Matches gh's mental model of a single 'current review state' rather
than Bitbucket's two-independent-toggles API. --approve and
--request-changes implicitly clear the inverse state so the reviewer
can flip cleanly; --withdraw clears whichever state is set.
Backend functions unchanged — they remain the primitives the command
layer composes. The command layer's secondary cleanup calls are
best-effort (swallow PullRequestError) so a primary-action success
isn't masked by an expected 'not currently in that state' failure on
the cleanup DELETE.
@b2lb2l changed the title BBC2-18 add bb pr approve / unapprove / request-changes / unrequest-changesBBC2-18 add bb pr review (-a / -r / -w) + surface approvals in bb pr viewApr 15, 2026
@b2l
b2l merged commit dd65274 into mainApr 15, 2026
@b2l
b2l deleted the b2lpowa/bbc2-18-approve-unapprove-and-request-changes branch April 15, 2026 13:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@b2l