Skip to content

fix(pull-requests): decode native Gitea reactions - #32

Open
kalvenschraut wants to merge 2 commits into
gitea/warning-checksfrom
gitea/reaction-content
Open

fix(pull-requests): decode native Gitea reactions#32
kalvenschraut wants to merge 2 commits into
gitea/warning-checksfrom
gitea/reaction-content

Conversation

@kalvenschraut

@kalvenschraut kalvenschraut commented Sep 5, 2026

Copy link
Copy Markdown
Member

Native Gitea reaction list rows use content, but the adapter decoded an optional reaction property. A successful reaction appeared optimistically and then vanished on refresh because every real row was silently discarded.

Decode Gitea's required content field and group reactions from it. Normalize the native null response for an empty reaction collection without dropping reactions fetched for other conversation subjects. The focused regressions use the native response shape, including user, content, and created_at, and verify viewer attribution and mixed empty/populated subjects through the API path.

Validation: 65 focused Gitea conversation and API tests passed. Targeted lint passed for all changed files. An integrated browser test against the native Gitea fixture confirmed reactions remained visible after a server restart and full-page reload, and that a native null empty description list did not hide a populated issue-comment reaction.

Implementation: Codex (GPT-6).

@kalvenschraut

Copy link
Copy Markdown
Member Author

@coderabbitai review

Please verify the native Gitea reaction response field against the adapter decoder, viewer attribution, pagination behavior, and regression coverage. This PR is stacked after #23 in stack pingdotgg#6.

@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== Pull request metadata ==='
gh pr view 32 --repo RTVision/t3code --json number,title,baseRefName,headRefName,body,files,commits

echo '=== Diff relative to PR base ==='
git diff --stat origin/"$(gh pr view 32 --repo RTVision/t3code --json baseRefName --jq .baseRefName)"...HEAD
git diff --find-renames --unified=80 origin/"$(gh pr view 32 --repo RTVision/t3code --json baseRefName --jq .baseRefName)"...HEAD -- .

echo '=== Candidate files ==='
fd -t f . | rg -i '(gitea|reaction|conversation|pull.request|comment|api)'

echo '=== Structural outlines ==='
fd -t f -e ts -e tsx . | rg -i '(gitea|reaction|conversation|pull.request|comment|api)' | while IFS= read -r file; do
  echo "--- $file"
  ast-grep outline "$file" --items all || true
done

Length of output: 3022


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== Pull request patch from GitHub ==='
gh pr diff 32 --repo RTVision/t3code --patch

echo '=== Structural outlines of changed files ==='
for file in \
  apps/server/src/pullRequest/GiteaConversation.ts \
  apps/server/src/pullRequest/GiteaConversation.test.ts \
  apps/server/src/pullRequest/GiteaPullRequestApi.test.ts
do
  echo "--- $file"
  ast-grep outline "$file" --items all
done

echo '=== Decoder and reaction call-path references ==='
rg -n -C 5 --type ts \
  '(content|reaction|created_at|viewer|currentUser|paginate|pagination|list.*reaction|reactions)' \
  apps/server/src/pullRequest/GiteaConversation.ts \
  apps/server/src/pullRequest/GiteaConversation.test.ts \
  apps/server/src/pullRequest/GiteaPullRequestApi.test.ts

Length of output: 86751


🤖 web_search executed:

Gitea API v1 list issue reactions response schema Reaction content user created_at OpenAPI

Length of output: 13906


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== Conversation-reaction implementation locations ==='
rg -n -C 8 --type ts \
  '(listConversationReactions|RawGiteaReaction|reactionsForViewer|paginate|paginated|x-total-count|link)' \
  apps/server/src/pullRequest/GiteaPullRequestApi.ts

echo '=== Conversation-reaction implementation ==='
implementation_line=$(rg -n --type ts 'listConversationReactions' apps/server/src/pullRequest/GiteaPullRequestApi.ts | head -1 | cut -d: -f1)
sed -n "$((implementation_line - 25)),$((implementation_line + 150))p" apps/server/src/pullRequest/GiteaPullRequestApi.ts

echo '=== Focused API regression tests ==='
sed -n '1540,1620p' apps/server/src/pullRequest/GiteaPullRequestApi.test.ts

echo '=== Focused conversation tests ==='
sed -n '1,90p' apps/server/src/pullRequest/GiteaConversation.test.ts

echo '=== All raw-reaction schema references ==='
rg -n -C 3 --type ts '\bRawGiteaReaction\b' apps/server/src

Length of output: 23851


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== Generic paginated-slice helper ==='
helper_line=$(rg -n --type ts 'const readUnknownSlice|function readUnknownSlice' apps/server/src/pullRequest/GiteaPullRequestApi.ts | head -1 | cut -d: -f1)
sed -n "$((helper_line - 15)),$((helper_line + 130))p" apps/server/src/pullRequest/GiteaPullRequestApi.ts

echo '=== Conversation reaction loader ==='
sed -n '1635,1725p' apps/server/src/pullRequest/GiteaPullRequestApi.ts

echo '=== Test runner metadata (not executed) ==='
fd -t f '^package\.json$' . -x sh -c '
  case "$1" in
    ./package.json|./apps/server/package.json)
      echo "--- $1"
      jq "{name, scripts}" "$1"
      ;;
  esac
' sh {}

echo '=== Test configuration files ==='
fd -t f -e ts -e js -e json . | rg '(^|/)(vitest|vite)\.config\.[^.]+$|package\.json$' | head -50

Length of output: 13522


@kalvenschraut Reviewed PR #32 with the stack context considered.

No findings.

  • Gitea documents content, created_at, and user for native issue reaction rows. The decoder now requires content. This matches the native API schema. Gitea API documentation
  • reactionsForViewer maps row.content. It trims actor logins. It compares the viewer login without case sensitivity.
  • listConversationReactions decodes each row through RawGiteaReaction before it calls reactionsForViewer.
  • The API regression covers pull-request reactions, issue-comment reactions, and viewer attribution through the API path.
  • The pagination regression verifies that x-total-count requests page 2 and preserves reactions from both native content rows.

I could not execute the focused tests in this inspection environment. The static review found the implementation and regression coverage consistent with the stated validation.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 7ef016a4-acf5-489f-99cd-e371bb0cb84d

📥 Commits

Reviewing files that changed from the base of the PR and between 93580c0 and c4fed16.

📒 Files selected for processing (3)
  • apps/server/src/pullRequest/GiteaConversation.test.ts
  • apps/server/src/pullRequest/GiteaConversation.ts
  • apps/server/src/pullRequest/GiteaPullRequestApi.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 1 remains after this review.


📝 Walkthrough

Walkthrough

Gitea reaction decoding now requires the native content field. Reaction grouping reads this field, and tests cover native response decoding, viewer detection, actor exclusion, timestamps, and pagination fixtures.

Changes

Gitea reaction handling

Layer / File(s) Summary
Update reaction parsing
apps/server/src/pullRequest/GiteaConversation.ts
RawGiteaReaction now requires content. Reaction grouping maps row.content through the provider content map.
Validate native reaction responses
apps/server/src/pullRequest/GiteaConversation.test.ts, apps/server/src/pullRequest/GiteaPullRequestApi.test.ts
Tests decode native Gitea reactions and validate grouping, viewer detection, actor exclusion, timestamps, and paginated fixtures.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to c4fed

Gitea reactions now decode and group from the native content field, preserving reactions after refresh while retaining viewer attribution and pagination behavior. The change is ready to merge.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 3 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the main change: decoding native Gitea reactions.
Description check ✅ Passed The description explains what changed, why it was needed, implementation details, regression coverage, and validation results. It does not include the template checklist, but the required change ratio…
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch gitea/reaction-content

Comment @coderabbitai help to get the list of available commands.

@kalvenschraut

Copy link
Copy Markdown
Member Author

@coderabbitai review

Please review the latest commit 5a889ffeb, especially the reaction-only nullAsEmpty scope and the regression proving one native null subject does not discard another subject's populated reaction list.

@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== Commit metadata and patch ==='
git show --no-ext-diff --format=fuller --find-renames --unified=100 5a889ffeb -- .

echo '=== Changed files ==='
git diff-tree --no-commit-id --name-only -r 5a889ffeb

echo '=== Candidate decoder/API/test outlines ==='
for file in $(git diff-tree --no-commit-id --name-only -r 5a889ffeb -- | rg '\.(ts|tsx)$'); do
  echo "--- $file"
  ast-grep outline "$file" --items all || true
done

echo '=== nullAsEmpty references and decoder call paths ==='
rg -n -C 8 --type ts '\bnullAsEmpty\b|\bRawGiteaReaction\b|\blistConversationReactions\b|\breactionsForViewer\b' apps/server/src

Length of output: 68388


@kalvenschraut Reviewed commit 5a889ffeb.

No findings.

  • nullAsEmpty is opt-in in readUnknownPage and readUnknownSlice.
  • listConversationReactions is the caller that enables nullAsEmpty: true.
  • Other list operations continue to require an array response.
  • A native null reaction response becomes an empty reaction list for only that target.
  • The new regression returns null for the pull-request reaction target and a populated native reaction row for issue:12.
  • The test verifies that pullRequest is empty and that bySubjectId.get("issue:12") still contains the heart reaction.

I did not run the test suite in this inspection environment. The static implementation and regression test match the requested behavior.

You are interacting with an AI system.

@kalvenschraut

Copy link
Copy Markdown
Member Author

@coderabbitai configuration

@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown

Configuration used: Path: .coderabbit.yaml

CodeRabbit configuration
# Source: defaults
language: en-US
# Source: defaults
tone_instructions: ''
# Source: defaults
early_access: false
# Source: defaults
enable_free_tier: true
# Source: defaults
inheritance: false
reviews:
  # Source: defaults
  profile: chill
  # Source: defaults
  request_changes_workflow: false
  # Source: defaults
  high_level_summary: true
  # Source: defaults
  high_level_summary_instructions: ''
  # Source: defaults
  high_level_summary_placeholder: '`@coderabbitai` summary'
  # Source: defaults
  high_level_summary_in_walkthrough: false
  # Source: defaults
  auto_title_placeholder: '`@coderabbitai`'
  # Source: defaults
  auto_title_instructions: ''
  # Source: Repository YAML (base)
  review_status: false
  # Source: defaults
  review_details: false
  # Source: defaults
  review_progress: true
  # Source: defaults
  commit_status: true
  # Source: defaults
  fail_commit_status: false
  # Source: defaults
  collapse_walkthrough: true
  # Source: defaults
  changed_files_summary: true
  # Source: defaults
  sequence_diagrams: true
  # Source: defaults
  estimate_code_review_effort: true
  # Source: defaults
  assess_linked_issues: true
  # Source: defaults
  related_issues: true
  # Source: defaults
  related_prs: true
  # Source: defaults
  suggested_labels: true
  # Source: defaults
  labeling_instructions: []
  # Source: defaults
  mutually_exclusive_groups: {}
  # Source: defaults
  auto_apply_labels: false
  # Source: defaults
  suggested_reviewers: true
  # Source: defaults
  auto_assign_reviewers: false
  # Source: defaults
  suggested_reviewers_instructions: []
  # Source: defaults
  in_progress_fortune: true
  # Source: defaults
  poem: false
  # Source: defaults
  enable_prompt_for_ai_agents: true
  # Source: defaults
  path_filters: []
  # Source: defaults
  path_instructions: []
  # Source: defaults
  abort_on_close: true
  # Source: defaults
  disable_cache: false
  slop_detection:
    # Source: defaults
    enabled: true
    # Source: defaults
    include_all_authors: false
  auto_review:
    # Source: Repository YAML (base)
    enabled: false
    # Source: defaults
    description_keyword: ''
    # Source: defaults
    auto_incremental_review: true
    # Source: defaults
    auto_pause_after_reviewed_commits: 5
    # Source: defaults
    ignore_title_keywords: []
    # Source: defaults
    labels: []
    # Source: defaults
    drafts: false
    # Source: defaults
    base_branches: []
    # Source: defaults
    ignore_usernames: []
  finishing_touches:
    docstrings:
      # Source: defaults
      enabled: true
    unit_tests:
      # Source: defaults
      enabled: true
    simplify:
      # Source: defaults
      enabled: false
    autofix:
      # Source: defaults
      enabled: true
    fix_ci:
      # Source: defaults
      enabled: true
    resolve_merge_conflict:
      # Source: defaults
      enabled: true
    # Source: defaults
    custom: []
  pre_merge_checks:
    # Source: defaults
    override_requested_reviewers_only: false
    docstrings:
      # Source: defaults
      mode: warning
      # Source: defaults
      threshold: 80
    title:
      # Source: defaults
      mode: warning
      # Source: defaults
      requirements: ''
    description:
      # Source: defaults
      mode: warning
    issue_assessment:
      # Source: defaults
      mode: warning
    # Source: defaults
    custom_checks: []
  # Source: defaults
  post_merge_actions: []
  tools:
    ast-grep:
      # Source: defaults
      rule_dirs: []
      # Source: defaults
      util_dirs: []
      # Source: defaults
      essential_rules: true
      # Source: defaults
      packages: []
    shellcheck:
      # Source: defaults
      enabled: true
    ruff:
      # Source: defaults
      enabled: true
    markdownlint:
      # Source: defaults
      enabled: true
    github-checks:
      # Source: defaults
      enabled: true
    languagetool:
      # Source: defaults
      enabled: true
      # Source: defaults
      enabled_rules: []
      # Source: defaults
      disabled_rules: []
      # Source: defaults
      enabled_categories: []
      # Source: defaults
      disabled_categories: []
      # Source: defaults
      enabled_only: false
      # Source: defaults
      level: default
    biome:
      # Source: defaults
      enabled: true
    hadolint:
      # Source: defaults
      enabled: true
    swiftlint:
      # Source: defaults
      enabled: true
    phpstan:
      # Source: defaults
      enabled: true
      # Source: defaults
      level: default
    phpmd:
      # Source: defaults
      enabled: true
    phpcs:
      # Source: defaults
      enabled: true
    golangci-lint:
      # Source: defaults
      enabled: true
    yamllint:
      # Source: defaults
      enabled: true
    gitleaks:
      # Source: defaults
      enabled: true
    trufflehog:
      # Source: defaults
      enabled: true
    checkov:
      # Source: defaults
      enabled: true
    tflint:
      # Source: defaults
      enabled: true
    detekt:
      # Source: defaults
      enabled: true
    eslint:
      # Source: defaults
      enabled: true
      e18e:
        # Source: defaults
        enabled: true
    flake8:
      # Source: defaults
      enabled: true
    fbinfer:
      # Source: defaults
      enabled: true
      # Source: defaults
      enable_java: false
    fortitudeLint:
      # Source: defaults
      enabled: true
    rubocop:
      # Source: defaults
      enabled: true
    buf:
      # Source: defaults
      enabled: true
    regal:
      # Source: defaults
      enabled: true
    actionlint:
      # Source: defaults
      enabled: true
    zizmor:
      # Source: defaults
      enabled: true
    pmd:
      # Source: defaults
      enabled: true
    clang:
      # Source: defaults
      enabled: true
    cppcheck:
      # Source: defaults
      enabled: true
    vale:
      # Source: defaults
      enabled: true
    verilator:
      # Source: defaults
      enabled: true
    opengrep:
      # Source: defaults
      enabled: true
    semgrep:
      # Source: defaults
      enabled: true
    circleci:
      # Source: defaults
      enabled: true
    clippy:
      # Source: defaults
      enabled: true
    sqlfluff:
      # Source: defaults
      enabled: true
    squawk:
      # Source: defaults
      enabled: true
    trivy:
      # Source: defaults
      enabled: true
    prismaLint:
      # Source: defaults
      enabled: true
    pylint:
      # Source: defaults
      enabled: true
    oxc:
      # Source: defaults
      enabled: true
    shopifyThemeCheck:
      # Source: defaults
      enabled: true
    luacheck:
      # Source: defaults
      enabled: true
    brakeman:
      # Source: defaults
      enabled: true
    dotenvLint:
      # Source: defaults
      enabled: true
    htmlhint:
      # Source: defaults
      enabled: true
    stylelint:
      # Source: defaults
      enabled: true
    checkmake:
      # Source: defaults
      enabled: true
    osvScanner:
      # Source: defaults
      enabled: true
    oasdiff:
      # Source: defaults
      enabled: true
    reactDoctor:
      # Source: defaults
      enabled: true
    presidio:
      # Source: defaults
      enabled: true
    blinter:
      # Source: defaults
      enabled: true
    smartyLint:
      # Source: defaults
      enabled: true
    emberTemplateLint:
      # Source: defaults
      enabled: true
    skillspector:
      # Source: defaults
      enabled: true
    psscriptanalyzer:
      # Source: defaults
      enabled: true
chat:
  # Source: defaults
  art: true
  # Source: defaults
  allow_non_org_members: true
  # Source: defaults
  auto_reply: true
  integrations:
    jira:
      # Source: defaults
      usage: auto
    linear:
      # Source: defaults
      usage: auto
knowledge_base:
  # Source: defaults
  opt_out: false
  web_search:
    # Source: defaults
    enabled: true
  code_guidelines:
    # Source: defaults
    enabled: true
    # Source: defaults
    filePatterns: []
  learnings:
    # Source: defaults
    scope: auto
    # Source: defaults
    approval_delay: 0
  issues:
    # Source: defaults
    scope: auto
  jira:
    # Source: defaults
    usage: auto
    # Source: defaults
    project_keys: []
    # Source: defaults
    excluded_project_keys: []
  linear:
    # Source: defaults
    usage: auto
    # Source: defaults
    team_keys: []
  pull_requests:
    # Source: defaults
    scope: auto
  mcp:
    # Source: defaults
    usage: auto
    # Source: defaults
    disabled_servers: []
  # Source: defaults
  automatic_linking_mode: disabled
  # Source: defaults
  automatic_repository_linking: false
  # Source: defaults
  linked_repositories: []
code_generation:
  docstrings:
    # Source: defaults
    language: en-US
    # Source: defaults
    path_instructions: []
  unit_tests:
    # Source: defaults
    path_instructions: []
issue_enrichment:
  auto_enrich:
    # Source: defaults
    enabled: false
  planning:
    # Source: defaults
    enabled: true
    auto_planning:
      # Source: defaults
      enabled: true
      # Source: defaults
      labels: []
  labeling:
    # Source: defaults
    labeling_instructions: []
    # Source: defaults
    auto_apply_labels: false

@kalvenschraut

Copy link
Copy Markdown
Member Author

@coderabbitai approve

@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown
✅ Action performed

Comments resolved. Approval is disabled; enable reviews.request_changes_workflow to allow explicit top-level @coderabbitai resolve or @coderabbitai approve commands.

Sign up for free to 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