Skip to content

feat(frontend): the person page is the person's own - #2340

Merged
dzarlax merged 8 commits into
constructorfabric:mainfrom
dzarlax:feat/person-page-is-theirs
Aug 10, 2026
Merged

feat(frontend): the person page is the person's own#2340
dzarlax merged 8 commits into
constructorfabric:mainfrom
dzarlax:feat/person-page-is-theirs

Conversation

@dzarlax

@dzarlax dzarlax commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Whose page this is

The person page is the person's own — for most readers it is the first screen
they meet, and they did not ask to be measured. That answers a question the
page had been getting wrong: what leads, what recedes, and what it owes the
reader before any number.

What changed

It names its own limits first. The page opened with four numbers and a red
list, and read as a complete picture of a person. For someone whose work leaves
few traces in the connected systems, that picture is mostly their chat and
their calendar. It now says which sections have no reading at all, above the
numbers — because the first question on your own page is what this thing knows
about you, and an answer at the bottom arrives after the page has already been
read as complete. "No data reaches us", not "no activity": only one of those is
something we know. No extra request — the standings are the ones the navigation
already asked for.

The tile leads with the comparison the reader owns. It carried two and
named neither: a badge against the person's own last period, a line against the
cohort median, so a bare "-13%" was readable as either. The reader did not
choose the cohort, cannot see who is in it, and cannot decide its median is the
right target — that is context. Their own last period is the one comparison
they can act on. Value in plain ink; under it, named and coloured, the change;
under that, quietly, the cohort. The badge is gone, and with it the header
clause that existed to explain it.

Each headline number carries where it has been. A single period plus two
comparisons cannot say whether something is new, recovering, or how it has
always been — three findings that call for three different conversations. A
line now sits beside each value, in the unit the reader picked.

Whose page it is decides what comes first. A standing is the larger claim
and leads by default, which is what a manager scanning someone else needs; on
your own page what moved this period leads, because that is what you can still
act on. Neither order hides anything.

Three deliberate refusals in the line

  • The running period is not drawn. A month four days old holds four days of
    work; beside finished months it invents a collapse, and nothing on a tile
    could tell the reader that the last point means something else.
  • Fewer than three readings draw nothing. Two points always make a straight
    line, and a straight line reads as a steady trend — but two is also exactly
    what a metric has when it has only just started being measured.
  • A missing reading breaks the line rather than dropping it to the floor: a
    period with no data must not be readable as a period with none of the thing.

The line is scaled to its own range and says only which way and how steadily —
the figures beside it carry the magnitude. It has a rule beneath it so it reads
as a span rather than floating, and the span is named once above the row, since
a tile has no room for an axis. Its request sits outside every loading and
error gate: the tiles render from the current period and the line appears when
it arrives, so the first screen never waits on it.

One thing removed

showSections gated the per-group cards and the drilldown sheets. The cards
moved to the navigation long ago, so what survived was a flag whose only
remaining effect was to make a click land nowhere on any screen that set it.
Removed; the sheets now follow the only question that matters — whether the
caller routes clicks elsewhere.

Not in this PR

Which metrics the headline row shows is unchanged: a fixed priority order,
filtered to what the person is observed for. For a role the order was not
written around, that shows what happens to be instrumented rather than what the
work is — which needs role expectations, not a layout change.

Verification

943 tests pass across both projects, tsc -b and eslint clean. Checked against
a running stand at several widths and periods.

Summary by CodeRabbit

  • New Features
    • KPI tiles now show trend charts, comparison periods, directional changes, and peer benchmarks.
    • Added messaging identifying sections without measured activity.
    • Added trend-range legends and clearer handling when comparison data is unavailable.
  • Improvements
    • Attention items prioritize recently changed metrics on a person’s own page while preserving existing ordering elsewhere.
    • Drilldown panels now open automatically unless navigation is handled directly.
  • Bug Fixes
    • Improved neutral, missing-data, and edge-case KPI display behavior.

Alexey Panfilov added 5 commits August 7, 2026 16:11
The page opened with four numbers and a red list, and read as a complete
picture of a person. For someone whose work leaves few traces in the connected
systems, that picture is mostly their chat and their calendar.

It now says what it cannot see before it says anything else: the sections with
no reading at all, named, above the numbers. Placed there because on a person's
own page the first question is what this thing knows about them, and a page
that answers it at the bottom has already been read as complete by the time the
answer arrives.

"No data reaches us", not "no activity": the two are different, and only one of
them is something we know. The standings behind the line are the ones the
navigation already asked for, so this costs no request.

Signed-off-by: Alexey Panfilov <alexey.panfilov@constructor.tech>
A tile carried two comparisons and named neither: a badge against the person's
own last period, and a line against the cohort median. A bare "-13%" could be
read as either, and the cohort read as the verdict.

On a person's own page that ranking is backwards. They did not choose the
cohort, cannot see who is in it, and cannot decide that its median is the right
target — so it is context. Their own last period is the one comparison they
chose and the only one they can act on.

The value is plain ink. Under it, coloured and named, the change since their
last period. Under that, quietly and without a verdict, the cohort. The badge
is gone: its content moved into the line, where it can say what it compares
against, which also retires the header clause that existed to explain it.

Signed-off-by: Alexey Panfilov <alexey.panfilov@constructor.tech>
Every number on the page was a single period plus two comparisons, and none of
them could say whether what the reader is looking at is new, recovering, or how
it has always been — three findings that call for three different
conversations.

A line now sits beside each headline value, in the unit the reader picked, and
three things about it are deliberate:

- The running period is not drawn. A month four days old holds four days of
  work; plotted beside finished months it invents a collapse, and nothing on a
  tile could tell the reader that the last point means something else.
- Fewer than three readings draw nothing. Two points always make a straight
  line, and a straight line reads as a steady trend — but two is also exactly
  what a metric has when it has only just started being measured.
- A missing reading breaks the line rather than dropping it to the floor: a
  period with no data must not be readable as a period with none of the thing.

The line is scaled to its own range and says only which way and how steadily —
the figures beside it carry the magnitude. It has a rule beneath it so it
claims a span of time rather than floating, and the span itself is named once
above the row, since a tile has no room for an axis.

The request sits outside every loading and error gate: the tiles render from
the current period and the line appears when it arrives. A first screen must
not wait on it.

Signed-off-by: Alexey Panfilov <alexey.panfilov@constructor.tech>
Three changes that only make sense together, all following from the person page
being the person's own.

**The attention block orders by who is reading.** A standing is the larger
claim and leads by default — what a manager scanning someone else needs. On
your own page the order inverts: what moved this period is what you can still
do something about, while what has been true for months you have already lived
through. Neither order hides anything; both kinds are always listed.

**A click on a number opens the section that owns it.** That was already the
intent, but a dead flag stood in the way: `showSections` gated the per-group
cards AND the drilldown sheets, and the cards moved to the navigation long ago.
What survived was a flag whose only remaining effect was to make a click land
nowhere on any screen that set it. Removed; the sheets now follow the one
question that matters — whether the caller routes clicks elsewhere.

**The page's own description is true again.** It promised per-section status
cards that have not been there since the sections moved to the navigation.

Signed-off-by: Alexey Panfilov <alexey.panfilov@constructor.tech>
The story asserted a bare "+9%", which the tile no longer renders: the change
now names what it is measured against, because unnamed it was readable as
either of the tile's two comparisons.

Signed-off-by: Alexey Panfilov <alexey.panfilov@constructor.tech>
@dzarlax
dzarlax requested a review from a team as a code owner August 7, 2026 14:36
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@dzarlax, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 26 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d1afd4fe-9474-4374-a8d3-16d4cde08786

📥 Commits

Reviewing files that changed from the base of the PR and between 8a9927c and 41976a2.

📒 Files selected for processing (10)
  • src/frontend/src/components/portal/metric-groups-view.tsx
  • src/frontend/src/components/widgets/dashboard/kpi-tile.test.tsx
  • src/frontend/src/components/widgets/dashboard/person-coverage.test.tsx
  • src/frontend/src/components/widgets/dashboard/person-coverage.tsx
  • src/frontend/src/components/widgets/dashboard/sparkline.test.tsx
  • src/frontend/src/components/widgets/dashboard/sparkline.tsx
  • src/frontend/src/lib/insight/group-data.ts
  • src/frontend/src/lib/portal/person-trend.test.ts
  • src/frontend/src/lib/portal/use-person-sections.test.tsx
  • src/frontend/src/lib/portal/use-person-sections.ts
📝 Walkthrough

Walkthrough

The PR adds person KPI trend data, sparkline rendering, coverage messaging, viewer-aware attention ordering, and updated KPI comparison text. It removes the showSections prop and updates related tests, stories, navigation documentation, and dashboard wiring.

Changes

Person KPI flow

Layer / File(s) Summary
Trend data and KPI wiring
src/frontend/src/lib/portal/person-trend.ts, src/frontend/src/components/portal/metric-groups-view.tsx, src/frontend/src/screens/dashboard.tsx
Trend utilities calculate completed buckets and nullable readings. MetricGroupsView retrieves trend data and passes it to each KPI tile with the active period noun.
KPI tile trend and comparison rendering
src/frontend/src/components/widgets/dashboard/kpi-tile.tsx, src/frontend/src/components/widgets/dashboard/sparkline.tsx, src/frontend/src/components/widgets/dashboard/kpi-tile.test.tsx, src/frontend/src/components/widgets/dashboard/kpi-tile.stories.tsx
KpiTile renders sparklines, period-specific delta text, and separate peer comparison text. Tests and stories cover the revised output.
Coverage and viewer-aware page behavior
src/frontend/src/components/widgets/dashboard/person-coverage.tsx, src/frontend/src/lib/insight/attention.ts, src/frontend/src/lib/insight/attention.test.ts, src/frontend/src/components/portal/metric-groups-view.tsx, src/frontend/src/components/portal/person-view.tsx
Person pages show uncovered sections, prioritize changed attention items for self-view, and render drilldown sheets without showSections. Navigation documentation reflects section-oriented behavior.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MetricGroupsView
  participant PersonTrend
  participant KpiTile
  MetricGroupsView->>PersonTrend: request completed person trend readings
  PersonTrend-->>MetricGroupsView: return nullable trend points
  MetricGroupsView->>KpiTile: pass trend points and period noun
  KpiTile-->>MetricGroupsView: render KPI value, sparkline, and comparisons
Loading

Possibly related PRs

Suggested reviewers: ktursunov, aleksdotbar, cyberantonz

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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.
Title check ✅ Passed The title clearly describes the main change: the person page prioritizes the viewing person and their own-page experience.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/frontend/src/components/widgets/dashboard/kpi-tile.test.tsx (1)

35-144: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add automated coverage for the new trend path.

The change adds trend into KpiTile, plus personTrendPoints, trendRange, runningBucketStart, and Sparkline, but src/frontend/src has no test coverage for these new ts/tsx lines. Also cover the trend branch in kapi-tile.test.tsx plus the helper and Sparkline cases.

🤖 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 `@src/frontend/src/components/widgets/dashboard/kpi-tile.test.tsx` around lines
35 - 144, Add automated tests for the new trend path: in
src/frontend/src/components/widgets/dashboard/kpi-tile.test.tsx (lines 35-144),
cover KpiTile rendering when trend data is provided; in
src/frontend/src/lib/portal/person-trend.ts (lines 19-86), test
personTrendPoints, trendRange, and runningBucketStart across normal and boundary
inputs; and in src/frontend/src/components/widgets/dashboard/sparkline.tsx
(lines 21-112), cover Sparkline rendering and its empty, single-point, and
range-handling cases.

Source: Coding guidelines

🤖 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 `@src/frontend/src/components/widgets/dashboard/person-coverage.tsx`:
- Around line 17-23: Add focused tests for PersonCoverage covering both
branches: assert it renders nothing when uncovered is empty, and assert the
coverage message includes the joined uncovered values when the array is
populated. Use the existing frontend component-testing conventions and keep the
tests scoped to this component’s behavior.

---

Outside diff comments:
In `@src/frontend/src/components/widgets/dashboard/kpi-tile.test.tsx`:
- Around line 35-144: Add automated tests for the new trend path: in
src/frontend/src/components/widgets/dashboard/kpi-tile.test.tsx (lines 35-144),
cover KpiTile rendering when trend data is provided; in
src/frontend/src/lib/portal/person-trend.ts (lines 19-86), test
personTrendPoints, trendRange, and runningBucketStart across normal and boundary
inputs; and in src/frontend/src/components/widgets/dashboard/sparkline.tsx
(lines 21-112), cover Sparkline rendering and its empty, single-point, and
range-handling cases.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a90540c3-c9d5-4998-8c97-2e18298841dc

📥 Commits

Reviewing files that changed from the base of the PR and between 29848f2 and 8a9927c.

📒 Files selected for processing (11)
  • src/frontend/src/components/portal/metric-groups-view.tsx
  • src/frontend/src/components/portal/person-view.tsx
  • src/frontend/src/components/widgets/dashboard/kpi-tile.stories.tsx
  • src/frontend/src/components/widgets/dashboard/kpi-tile.test.tsx
  • src/frontend/src/components/widgets/dashboard/kpi-tile.tsx
  • src/frontend/src/components/widgets/dashboard/person-coverage.tsx
  • src/frontend/src/components/widgets/dashboard/sparkline.tsx
  • src/frontend/src/lib/insight/attention.test.ts
  • src/frontend/src/lib/insight/attention.ts
  • src/frontend/src/lib/portal/person-trend.ts
  • src/frontend/src/screens/dashboard.tsx

Comment on lines +17 to +23
export function PersonCoverage({ uncovered }: PersonCoverageProps) {
if (uncovered.length === 0) return null;
return (
<p className="text-xs text-muted-foreground">
No data reaches us for {uncovered.join(", ")} — this page shows what is
measured, not everything this person does.
</p>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate relevant files =="
fd -a 'person-coverage\.tsx$' . | sed 's#^\./##' || true
fd -a 'package\.json$|coverage|test|vitest|jest' . | sed 's#^\./##' | head -200

echo
echo "== component file =="
for f in $(fd 'person-coverage\.tsx$' .); do
  echo "--- $f"
  wc -l "$f"
  cat -n "$f"
done

echo
echo "== references to PersonCoverage =="
rg -n "PersonCoverage|personCoverage|person-coverage" . --glob '!node_modules' --glob '!dist' --glob '!build' || true

echo
echo "== search coverage-related files =="
git ls-files | rg -i 'coverage|coverage-report|vitest|jest|testcoverage|lcov|codecov' | head -200

echo
echo "== frontend package scripts =="
for f in $(git ls-files '*package.json'); do
  echo "--- $f"
  python3 - <<PY
import json, pathlib, sys
p=pathlib.Path("$f")
try:
    data=json.loads(p.read_text())
except Exception as e:
    print("json error", e)
    raise SystemExit
for section in ("scripts","devDependencies","dependencies","jest","vitest"):
    if section in data:
        print(f"[{section}]")
        v=data[section]
        if isinstance(v, dict):
            for k,vv in v.items():
                if any(s in k or s in str(vv) for s in ("test","coverage","frontend","vitest","jest")):
                    print(f"{k}: {vv}")
        else:
            print(v)
PY
done

echo
echo "== tests mentioning PersonCoverage or dashboard widgets =="
rg -n "PersonCoverage|personCoverage|coverage|no data reaches us|uncovered" . --glob '!node_modules' --glob '!dist' --glob '!build' || true

Repository: constructorfabric/insight

Length of output: 15347


🏁 Script executed:

#!/bin/bash
set -eu0o pipefail

# Inspect repository coverage/test evidence without running repository code.
echo "== git status/stat =="
git status --short || true
git diff --stat || true

echo
echo "== tracked coverage/test config files =="
git ls-files | rg -n '^(coverage|src/frontend/.*/.*\.?(config|test)|.*(test|spec|coverage|vitest|jest).*\.(ts|tsx|js|mjs|cjs|json|ya?ml))$|vitest|jest|coverage' | head -300

echo
echo "== exact component path from git ls-files =="
git ls-files | rg 'person-coverage\.tsx$|package\.json$' | head -200

Repository: constructorfabric/insight

Length of output: 17930


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== src/frontend/src/components/widgets/dashboard/person-coverage.tsx =="
wc -l src/frontend/src/components/widgets/dashboard/person-coverage.tsx
cat -n src/frontend/src/components/widgets/dashboard/person-coverage.tsx

echo
echo "== candidate nearby/person coverage tests =="
git ls-files | rg 'src/frontend/src/components/widgets/dashboard/(person-coverage|.*coverage|.*test)\.(ts|tsx)$|src/frontend/src/components/widgets/dashboard/.*/.*test\.tsx$|src/frontend/src/.*test\.tsx$' | rg -i 'coverage|person' || true

echo
echo "== all occurrences of PersonCoverage ==="
rg -n "PersonCoverage" src/frontend || true

echo
echo "== package dev/test/coverage scripts =="
python3 - <<'PY'
import json
from pathlib import Path
p=Path("src/frontend/package.json")
data=json.loads(p.read_text())
for section in ("scripts","devDependencies","dependencies"):
    print(f"[{section}]")
    for k, v in data.get(section, {}).items():
        if any(s in k or s in str(v).lower() for s in ("test","coverage","vitest","frontend")):
            print(f"{k}: {v}")
PY

echo
echo "== vitest config coverage settings =="
cat -n src/frontend/vitest.config.ts | sed -n '1,220p'

Repository: constructorfabric/insight

Length of output: 8991


Add coverage for the PersonCoverage empty and rendered branches.

PersonCoverage has a no-data branch and a rendered message branch, but there is no test evidence for either path. Add focused tests for both branches so new frontend changes stay at 80% coverage.

🤖 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 `@src/frontend/src/components/widgets/dashboard/person-coverage.tsx` around
lines 17 - 23, Add focused tests for PersonCoverage covering both branches:
assert it renders nothing when uncovered is empty, and assert the coverage
message includes the joined uncovered values when the array is populated. Use
the existing frontend component-testing conventions and keep the tests scoped to
this component’s behavior.

Source: Coding guidelines

Alexey Panfilov added 2 commits August 7, 2026 17:05
… the wrong one

The coverage line said "no data reaches us" of every section with no reading.
Two different things arrive that way, and only one of them is that sentence:
a source nothing is wired to, and a source that works fine and holds none of
this person's work. Told the second reader their tracker is broken while their
colleagues' numbers for that same section sat one screen away.

The pool tells them apart and was already in the response. A peer view counts
the entities that had a reading, so an empty pool across every metric of a
section means nobody is measured there, while a pool that reads means the
measurement works and this person is absent from it.

Not proof — a section whose whole cohort was idle looks the same — but it
fails toward the weaker claim, which is the safe direction. Understating what
we know costs a reader nothing; calling a working source broken costs them
their trust in the page.

Signed-off-by: Alexey Panfilov <Alexey.Panfilov@constructor.tech>
Every rule in the trend path is a refusal, and a refusal looks like a missing
feature from the outside. Left untested, "why is there no line here" gets
answered by deleting the rule.

So each one is now a test that says what it costs to remove: the running
period is dropped because a four-day month drawn beside finished months reads
as a collapse the calendar invented; fewer than three readings draw nothing
because two points always make a straight line and a straight line reads as a
steady trend; a missing reading breaks the line because a period with no data
must not read as a period with none of the thing. A gap cannot buy its way
past the minimum either.

The tile is covered for what it must not do while readings are in flight: the
numbers render first and the line appears when it arrives, so the first screen
never waits on a second request. The line carries a data-slot so a test can
tell it from the arrow in the delta.

Signed-off-by: Alexey Panfilov <Alexey.Panfilov@constructor.tech>
@dzarlax

dzarlax commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Both findings were right — neither path had a test. Addressed in ebb1b22 and 374f257.

Trend path. Added person-trend.test.ts (11 cases) and sparkline.test.tsx (5), plus two cases on the tile. What they pin is deliberately the refusals, because each one looks like a missing feature from the outside and would otherwise be "fixed" by deleting it: the running period is not drawn, fewer than three readings draw nothing, a gap breaks the line rather than dropping it to the floor, and a gap cannot buy its way past the minimum. The tile is covered for rendering its numbers before any readings arrive and for taking a null trend as "no line" rather than substituting one of its own. The sparkline now carries a data-slot so a test can tell it from the arrow icon in the delta.

PersonCoverage. Tested, and testing it turned up a defect worth more than the missing coverage. The component said "no data reaches us" of every section with no reading, but two different things arrive that way: a source nothing is wired to, and a source that works and holds none of this person's work. The second reader was being told their tracker is broken while their colleagues' numbers for that same section sit one screen away.

The signal was already in the response — a peer view counts the entities that had a reading, so an empty pool across every metric of a section means nobody is measured there, and a pool that reads means the measurement works and this person is absent from it. Two sentences now, never the wrong one. It is not proof (a wholly idle cohort looks the same) but it fails toward the weaker claim, which is the safe direction.

966 tests pass across both projects, tsc -b and eslint clean.

@dzarlax
dzarlax added this pull request to the merge queue Aug 10, 2026
Merged via the queue into constructorfabric:main with commit 3c74841 Aug 10, 2026
50 of 52 checks passed
@dzarlax
dzarlax deleted the feat/person-page-is-theirs branch August 10, 2026 12:01
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.

2 participants