Skip to content

Implement operator-aware stat conditions support#182

Merged
mikkisguy merged 11 commits into
mainfrom
172-parser-extract-comparison-operators
Jun 1, 2026
Merged

Implement operator-aware stat conditions support#182
mikkisguy merged 11 commits into
mainfrom
172-parser-extract-comparison-operators

Conversation

@mikkisguy

@mikkisguy mikkisguy commented May 31, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Conditions now include comparison operators and display operator symbols (≥, ≤, >, <, ==, ≠); legacy numeric thresholds are normalized to use a default operator (≥).
  • Documentation

    • Added design spec for operator-aware condition parsing, normalization, and UI rendering.
  • Tests

    • Expanded unit and UI tests and seeded test data to cover all comparison operators.
  • Chores

    • Minor test environment and TypeScript config adjustments.

mikkisguy added 9 commits May 31, 2026 12:59
Fix build-breaking type change from Task 1. The spec did not require
refactoring PublicLabel.conditions to use LineConditions. This change
caused compilation errors in the label properties UI which expects
numeric stats.

- Revert PublicLabel.conditions to inline shape with numeric stats
- Keep LineConditions, ComparisonOperator, StatCondition unchanged
- Update rpy-parser to capture comparison operators from stat conditions
- Update TechnicalConstructs interface to use StatCondition
- Add normalization functions to convert numbers to StatCondition with default operator
- Update label_lines schema to support both number and StatCondition
- Update tests to match new shape

Passes all 656 backend unit tests.
@mikkisguy mikkisguy self-assigned this May 31, 2026
@mikkisguy mikkisguy linked an issue May 31, 2026 that may be closed by this pull request
4 tasks
@coderabbitai

coderabbitai Bot commented May 31, 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: Pro

Run ID: aaf46211-d7d0-428c-b897-a80ef1254c83

📥 Commits

Reviewing files that changed from the base of the PR and between 632e3d2 and e313855.

📒 Files selected for processing (1)
  • apps/backend/src/services/labels.service.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/backend/src/services/labels.service.ts

📝 Walkthrough

Walkthrough

This PR implements end-to-end support for operator-aware stat conditions. The parser now extracts comparison operators (>=, <=, >, <, ==, !=) from if/elif expressions; the backend normalizes legacy numeric conditions to structured { value, operator } objects with a default >= operator; and the frontend displays operator symbols (e.g., , ) alongside stat values in badges and popovers.

Changes

Operator-aware stat conditions (parsing, persistence, and UI display)

Layer / File(s) Summary
Shared types and design specification
packages/shared/src/index.ts, docs/superpowers/specs/2026-05-31-parser-comparison-operators-design.md
New ComparisonOperator and StatCondition types define the shape for { value, operator } stat conditions. Design spec documents the full feature including data model, backward compatibility via default >= operator, and test expectations.
Parser: Extract operators from if/elif conditions
apps/backend/src/services/rpy-parser.service.ts, apps/backend/src/services/__tests__/technical-parser.service.unit.test.ts
extractTechnicalConstructs now parses comparison operators from if/elif stat expressions and stores { value, operator } pairs. New test suite covers all six operators, nested conditions with parentheses, and mixed stats with variables.
Backend: Schema, normalization, and persistence
apps/backend/src/db/schema/tables/label-lines.ts, apps/backend/src/services/label-line-mapper.ts, apps/backend/src/services/__tests__/label-line-mapper.service.unit.test.ts, apps/backend/src/services/labels.service.ts
Database schema accepts StatCondition type. Label-line mapper introduces DEFAULT_OPERATOR (>=) and normalization helpers to convert numeric stat conditions to structured form, persisting normalized conditions for each label line. Mapper and labels service convert and expose normalized operator-aware shapes. Tests cover operator preservation and numeric normalization.
Backend: Seed data and test documentation
apps/backend/scripts/seed-technical-badges.ts
Seeded test data updated to use { value, operator } shape across lines 3, 8, and 10; line 10 covers all six operators. Post-seed console output documents operator coverage and directs manual testing of operator symbol badges.
Frontend: Type updates and condition normalization
apps/frontend/src/lib/prose-types.ts, apps/frontend/src/hooks/useTechnicalInfo.ts
DialogueEntry.technicalInfo.conditions.stats now typed as Record<string, StatCondition>. useTechnicalInfo hook normalizes incoming numeric conditions to structured form with default >= operator before returning technical info.
Frontend: UI rendering with operator symbols
apps/frontend/src/components/write-mode/TechnicalPopover.tsx, apps/frontend/src/components/write-mode/LabelPropertiesPanel.tsx, apps/frontend/src/components/write-mode/TechnicalBadge.tsx, apps/frontend/src/components/__tests__/TechnicalPopover.test.tsx
Updated components render operator symbols (≥, ≤, >, <, ==, ≠) alongside stat values via new formatStatCondition helpers backed by OPERATOR_SYMBOLS mapping. Test verifies symbol rendering for >= and != operators.
Test infrastructure: globalThis compatibility and tsconfig
apps/frontend/src/contexts/__tests__/ThemeContext.test.tsx, apps/frontend/src/lib/api/__tests__/auth.unit.test.ts, apps/frontend/src/lib/api/__tests__/gitlab.unit.test.ts, apps/frontend/src/lib/api/__tests__/variables.unit.test.ts, apps/frontend/tsconfig.json
Updated fetch mock setup across test files to use globalThis.fetch instead of global.fetch for runtime compatibility. Added Testing Library typings to tsconfig.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related issues

Possibly related PRs

  • mikkisguy/branchforge#168: Updates LabelPropertiesPanel.tsx to render operator-aware stat conditions, extending an earlier PR that replaced CharacterReferencePanel with LabelPropertiesPanel.
  • mikkisguy/branchforge#169: Introduces the line-level conditions feature including seeded test data and mapper logic; this PR extends the same pipeline to support { value, operator } shapes.

Poem

🐰 Operators hop from parser to view,
"≥" and "≠" now wear their cue,
Seeded lines show every sign,
Normalized stats keep the data fine,
Badges sparkle with comparison truth.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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 Title check skipped as CodeRabbit has written the PR title.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 172-parser-extract-comparison-operators

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot changed the title @coderabbitai Implement operator-aware stat conditions support May 31, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (5)
apps/frontend/tsconfig.json (1)

18-18: 💤 Low value

Consider removing vitest/globals from types array.

The @testing-library/jest-dom entry is correct and necessary for matchers like toBeInTheDocument() used throughout the tests.

However, all test files explicitly import test functions from vitest:

import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";

Adding vitest/globals to the types array is intended for projects that use global describe/it/expect without importing them. Since this codebase uses explicit imports, including vitest/globals may be unnecessary and could cause confusion about whether globals or imports should be used.

Consider this alternative if globals aren't needed
-    "types": ["vitest/globals", "`@testing-library/jest-dom`"],
+    "types": ["`@testing-library/jest-dom`"],

If you prefer to switch to globals instead, you could remove the explicit imports from test files and keep vitest/globals in the types array.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/frontend/tsconfig.json` at line 18, The tsconfig "types" array currently
includes "vitest/globals" even though test files explicitly import test helpers
from "vitest"; remove "vitest/globals" from the types array (leaving
"`@testing-library/jest-dom`") so type resolution matches the code style, or
alternatively remove explicit imports in test files and keep "vitest/globals" if
you intentionally want globals—update the entry in the tsconfig types list
accordingly.
apps/frontend/src/components/__tests__/TechnicalPopover.test.tsx (1)

1-3: ⚡ Quick win

Use the frontend @/ alias for the component import.

Please switch the TechnicalPopover import to the repo-standard src alias instead of a relative path.

As per coding guidelines, Frontend: Use @/ alias for src directory imports: import { thing } from "@/lib/thing".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/frontend/src/components/__tests__/TechnicalPopover.test.tsx` around
lines 1 - 3, The test imports TechnicalPopover via a relative path; update the
import in TechnicalPopover.test.tsx to use the frontend src alias (use "`@/`..."
style) so the TechnicalPopover component is imported via the repo-standard alias
(referencing the TechnicalPopover symbol in the test) instead of
"../write-mode/TechnicalPopover".
apps/backend/src/services/__tests__/label-line-mapper.service.unit.test.ts (1)

69-119: ⚡ Quick win

Add one case that keeps statDeltas intact.

These new assertions cover operator normalization, but the helper now rebuilds the conditions object. A single test with both stats and statDeltas would catch accidental field loss on the parser-to-mapper path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/backend/src/services/__tests__/label-line-mapper.service.unit.test.ts`
around lines 69 - 119, Add a new unit test that passes an entry to
mapEntriesToLabelLineValues containing both conditions.stats (as a plain number
or with non-default operator) and conditions.statDeltas, then assert that
result[0].conditions preserves statDeltas exactly and also normalizes stats
(e.g., stats.affection_luna becomes { value: 50, operator: ">=" } or preserves a
provided operator like ">"); this will exercise the parser-to-mapper path and
prevent accidental loss of the statDeltas field when mapEntriesToLabelLineValues
rebuilds conditions.
apps/backend/src/services/__tests__/technical-parser.service.unit.test.ts (1)

98-140: ⚡ Quick win

Add one explicit elif operator case.

This suite covers the new operator shapes well, but every added example still goes through if. Since the feature is scoped to if/elif parsing, one elif ...: assertion here would guard the separate branch from drifting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/backend/src/services/__tests__/technical-parser.service.unit.test.ts`
around lines 98 - 140, Add a dedicated test that uses an "elif" line (e.g.,
"elif affection_luna >= 50:") to assert extractTechnicalConstructs handles elif
the same as if; update the tests in technical-parser.service.unit.test.ts by
adding a new it block that calls extractTechnicalConstructs with an elif
condition and expects result.conditions.stats to contain the same operator/value
mapping (reference extractTechnicalConstructs and the existing "captures >=
operator" test as the template).
apps/backend/scripts/seed-technical-badges.ts (1)

335-355: ⚡ Quick win

Keep the seed fixtures ordered by sequence.

The new sequence: 10 record is inserted before the sequence: 9 block, so the array no longer matches the numbered comments or the manual test steps. Moving this block below Line 9 will keep the fixture easier to read and verify.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/backend/scripts/seed-technical-badges.ts` around lines 335 - 355, The
new fixture object with sequence: 10 (identifier: labelId, sequence, conditions)
is placed before the existing sequence: 9 block, breaking the ordered sequence;
move the entire object whose sequence is 10 so it appears after the block with
sequence: 9 so the array remains sorted by sequence and matches the numbered
comments and manual test steps, ensuring you keep the object intact (including
its conditions.stats entries) and update surrounding comments if necessary.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/backend/src/services/label-line-mapper.ts`:
- Around line 33-36: The exported LineConditions type is missing statDeltas
which the parser function extractTechnicalConstructs still emits as
conditions.statDeltas; update LineConditions to include a statDeltas? field (the
same shape as stats, e.g. Record<string, number | StatCondition>) so parser
output can be passed through without casts and runtime normalization that
spreads extra keys remains type-safe.

In `@apps/frontend/src/lib/api/__tests__/gitlab.unit.test.ts`:
- Line 13: The test uses an unnecessary double-cast when assigning the mock
fetch; replace the line that assigns globalThis.fetch (currently using mockFetch
as unknown as typeof fetch) with a direct assignment to globalThis.fetch =
mockFetch; locate the assignment referencing globalThis.fetch and the mockFetch
variable in the gitlab unit test (function/test block where mockFetch is
defined) and remove the redundant casts so the code matches auth.unit.test.ts
and variables.unit.test.ts.

In `@packages/shared/src/index.ts`:
- Around line 331-340: PublicLabel's conditions.stats is still declared as
Record<string, number> while LineConditions.stats was changed to Record<string,
StatCondition>, causing an API mismatch; update the PublicLabel type
(specifically the PublicLabel.conditions.stats declaration) to use
Record<string, StatCondition> so it matches StatCondition and LineConditions,
and search for any usages of PublicLabel (e.g., serialization/deserialization,
tests, or consumers) to update or adapt them to the new StatCondition shape.

---

Nitpick comments:
In `@apps/backend/scripts/seed-technical-badges.ts`:
- Around line 335-355: The new fixture object with sequence: 10 (identifier:
labelId, sequence, conditions) is placed before the existing sequence: 9 block,
breaking the ordered sequence; move the entire object whose sequence is 10 so it
appears after the block with sequence: 9 so the array remains sorted by sequence
and matches the numbered comments and manual test steps, ensuring you keep the
object intact (including its conditions.stats entries) and update surrounding
comments if necessary.

In `@apps/backend/src/services/__tests__/label-line-mapper.service.unit.test.ts`:
- Around line 69-119: Add a new unit test that passes an entry to
mapEntriesToLabelLineValues containing both conditions.stats (as a plain number
or with non-default operator) and conditions.statDeltas, then assert that
result[0].conditions preserves statDeltas exactly and also normalizes stats
(e.g., stats.affection_luna becomes { value: 50, operator: ">=" } or preserves a
provided operator like ">"); this will exercise the parser-to-mapper path and
prevent accidental loss of the statDeltas field when mapEntriesToLabelLineValues
rebuilds conditions.

In `@apps/backend/src/services/__tests__/technical-parser.service.unit.test.ts`:
- Around line 98-140: Add a dedicated test that uses an "elif" line (e.g., "elif
affection_luna >= 50:") to assert extractTechnicalConstructs handles elif the
same as if; update the tests in technical-parser.service.unit.test.ts by adding
a new it block that calls extractTechnicalConstructs with an elif condition and
expects result.conditions.stats to contain the same operator/value mapping
(reference extractTechnicalConstructs and the existing "captures >= operator"
test as the template).

In `@apps/frontend/src/components/__tests__/TechnicalPopover.test.tsx`:
- Around line 1-3: The test imports TechnicalPopover via a relative path; update
the import in TechnicalPopover.test.tsx to use the frontend src alias (use
"`@/`..." style) so the TechnicalPopover component is imported via the
repo-standard alias (referencing the TechnicalPopover symbol in the test)
instead of "../write-mode/TechnicalPopover".

In `@apps/frontend/tsconfig.json`:
- Line 18: The tsconfig "types" array currently includes "vitest/globals" even
though test files explicitly import test helpers from "vitest"; remove
"vitest/globals" from the types array (leaving "`@testing-library/jest-dom`") so
type resolution matches the code style, or alternatively remove explicit imports
in test files and keep "vitest/globals" if you intentionally want globals—update
the entry in the tsconfig types list accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 378a84a8-fc74-496e-82ab-6a48059ad478

📥 Commits

Reviewing files that changed from the base of the PR and between 122b9bf and 1fc4b35.

📒 Files selected for processing (19)
  • apps/backend/scripts/seed-technical-badges.ts
  • apps/backend/src/db/schema/tables/label-lines.ts
  • apps/backend/src/services/__tests__/label-line-mapper.service.unit.test.ts
  • apps/backend/src/services/__tests__/technical-parser.service.unit.test.ts
  • apps/backend/src/services/label-line-mapper.ts
  • apps/backend/src/services/rpy-parser.service.ts
  • apps/frontend/src/components/__tests__/TechnicalPopover.test.tsx
  • apps/frontend/src/components/write-mode/LabelPropertiesPanel.tsx
  • apps/frontend/src/components/write-mode/TechnicalBadge.tsx
  • apps/frontend/src/components/write-mode/TechnicalPopover.tsx
  • apps/frontend/src/contexts/__tests__/ThemeContext.test.tsx
  • apps/frontend/src/hooks/useTechnicalInfo.ts
  • apps/frontend/src/lib/api/__tests__/auth.unit.test.ts
  • apps/frontend/src/lib/api/__tests__/gitlab.unit.test.ts
  • apps/frontend/src/lib/api/__tests__/variables.unit.test.ts
  • apps/frontend/src/lib/prose-types.ts
  • apps/frontend/tsconfig.json
  • docs/superpowers/specs/2026-05-31-parser-comparison-operators-design.md
  • packages/shared/src/index.ts

Comment thread apps/backend/src/services/label-line-mapper.ts
Comment thread apps/frontend/src/lib/api/__tests__/gitlab.unit.test.ts Outdated
Comment thread packages/shared/src/index.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/backend/src/services/labels.service.ts`:
- Around line 1732-1746: The current transformation in transformedConditions
(mapping label.conditions.stats) always wraps each stat into { value, operator:
">=" }, which double-wraps already-normalized StatCondition objects produced by
normalizeLineConditions; update the mapping in the block producing
transformedConditions to detect if the original value is already a StatCondition
(e.g., an object with keys like "value" and "operator") and leave it as-is,
otherwise wrap legacy numeric values into a StatCondition with operator ">=";
keep the variables copy logic unchanged and ensure the resulting stats type
matches PublicLabel["conditions"].
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cc38b435-3fb9-409b-97b7-ce20e24ec963

📥 Commits

Reviewing files that changed from the base of the PR and between 1fc4b35 and 632e3d2.

📒 Files selected for processing (9)
  • apps/backend/scripts/seed-technical-badges.ts
  • apps/backend/src/services/__tests__/label-line-mapper.service.unit.test.ts
  • apps/backend/src/services/__tests__/technical-parser.service.unit.test.ts
  • apps/backend/src/services/label-line-mapper.ts
  • apps/backend/src/services/labels.service.ts
  • apps/frontend/src/components/__tests__/TechnicalPopover.test.tsx
  • apps/frontend/src/lib/api/__tests__/gitlab.unit.test.ts
  • apps/frontend/tsconfig.json
  • packages/shared/src/index.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/frontend/src/lib/api/tests/gitlab.unit.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/frontend/src/components/tests/TechnicalPopover.test.tsx
  • packages/shared/src/index.ts
  • apps/backend/scripts/seed-technical-badges.ts

Comment thread apps/backend/src/services/labels.service.ts
@mikkisguy mikkisguy merged commit 2972c11 into main Jun 1, 2026
5 checks passed
@mikkisguy mikkisguy deleted the 172-parser-extract-comparison-operators branch June 1, 2026 15:53
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.

Parser: extract comparison operators in line-level conditions

1 participant