Skip to content

refactor: AreaHeader extraction + one typed contacts object (#1176) - #1212

Merged
escapedcat merged 4 commits into
mainfrom
refactor/area-page-split
Aug 4, 2026
Merged

refactor: AreaHeader extraction + one typed contacts object (#1176)#1212
escapedcat merged 4 commits into
mainfrom
refactor/area-page-split

Conversation

@escapedcat

@escapedcat escapedcat commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

The #1176 split, scoped per the re-review on the issue. Stacked on #1211 (refactor/places-in-area) — GitHub retargets to main when it merges; only the last two commits are new here. Refs #1176 (the remaining slice — per-route section components — is a judgment call once this lands; see below).

Commit 1 — Socials takes one typed contacts object

  • extractContacts moves to $lib/area/contacts.ts, shared by the SSR bundle (areaSectionLoad), the communities-map popups, and CommunityCard — which each carried their own 22-line contact:* fan-out. All three now pass contacts: AreaContacts.
  • Socials' interface collapses from 22 named props to one object; its internals (per-key sanitization, template) are unchanged — only the seam moved.

Commit 2 — AreaHeader extraction; derive, don't initialize

  • The header half of the 544-line AreaPage becomes AreaHeader.svelte: prop-in/markup-out, with every value derived from the SSR bundle — no imperative init, no reset lifecycle. An area navigation swaps data and everything follows.
  • That deletes initializeData wholesale, the 22 contact locals, dataInitialized (each child now gets an honest signal: merchant highlights gate on sweep completion, the issues table's loading is false because issues arrive with the maintain route's own load), and most of the manual reset block — only the async machinery (taggers fetch, containment sweep) still needs explicit resets.
  • The three parallel section Records collapse into one SECTIONS array: the section id is the route slug is the i18n key suffix.
  • Latent bug fixed: alias/name were consts frozen at first mount, while the component instance is reused across client-side area navigations — stale header identity after any /community/X → /community/Y transition.

Deliberately NOT done (the re-review's constraints):

  • No {#key} boundary. AreaMap's instance-reuse path (setData + animated fitBounds across area navs) must survive; a keyed teardown would refetch style + tiles per navigation — a visible regression. The (now much smaller) reset block is the accepted cost.
  • reportsSync stays page-level — the merchants section consumes the report-fed AreaMap stars (fix(area): grade the area map stars from the real report data #1163 #1186), so pushing it into stats-only would leave them pulsing forever.
  • Full per-route section components: what remains of AreaPage after this is ~330 lines of section wiring; whether that final slice pays for itself is worth judging against the post-merge file, so the issue stays open.

Test plan

  • pnpm run format:fix / check / lint clean, 557/557 unit tests
  • SSR smoke via dev server: community page renders #profile header, name, and verified chip; country stats 200
  • CI (build, e2e, type-checks)
  • Manual browser QA: community + country pages (header, socials, tip, verify-link jump to the maintain form), communities index cards, communities-map popups, cross-area client navigation (header follows, no stale name)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added area header component displaying profile information, including avatar, name, description, organization details, verification status indicators, and contact information for geographic areas and communities.
  • Bug Fixes

    • Fixed issues table not reflecting updates when data changes.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds AreaHeader, uses SSR area data in AreaPage, consolidates contact data into AreaContacts, and refreshes IssuesTable when issue props change.

Changes

Area rendering and contact data

Layer / File(s) Summary
Consolidated contact extraction and rendering
src/lib/area/contacts.ts, src/components/Socials.svelte, src/lib/areaSectionLoad.ts, src/routes/communities/...
Contact tags are extracted into typed AreaContacts objects. Community views pass the object to Socials, which sanitizes and renders its properties.
SSR-backed area header and navigation
src/components/area/AreaHeader.svelte, src/components/area/AreaPage.svelte
AreaHeader renders area profile, community verification, contacts, and tips. AreaPage uses SSR data, literal section slugs, and reactive area state.
Issue table refresh
src/components/area/AreaPage.svelte, src/components/IssuesTable.svelte
AreaPage removes initialization gating for activity data. IssuesTable resets its rendered state when the issues prop changes.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant AreaPage
  participant AreaHeader
  participant Socials
  participant Tip
  AreaPage->>AreaHeader: Pass SSR area data
  AreaHeader->>Socials: Pass consolidated contacts
  AreaHeader->>Tip: Pass lightning tip destination
  AreaPage->>AreaPage: Update section and area state
Loading

Suggested reviewers: dadofsambonzuki

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'refactor: AreaHeader extraction + one typed contacts object (#1176)' accurately summarizes the main changes: extracting AreaHeader and consolidating contacts into a typed object.
Description check ✅ Passed The description covers all required template sections and provides detailed technical rationale, test results, and known constraints. It addresses the related issue (#1176), explains proposed changes with commit-level detail, and notes pending CI validation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/area-page-split

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.

@escapedcat
escapedcat marked this pull request as ready for review August 4, 2026 14:35
@escapedcat
escapedcat requested a lite review from Copilot August 4, 2026 14:35
@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Refactor area pages: extract AreaHeader and unify contacts into typed object

✨ Enhancement 🐞 Bug fix 🕐 40+ Minutes

Grey Divider

AI Description

• Collapse Socials from 22 props to one typed contacts object via a shared helper.
• Extract AreaHeader and derive all header state from SSR data across navigations.
• Fix stale header identity and simplify section routing/reset + sweep gating in AreaPage.
Diagram

graph TD
  L["areaSectionLoad"] --> C("extractContacts") --> P["AreaPage data.contacts"] --> H["AreaHeader"] --> S["Socials"]
  C --> CC["CommunityCard"] --> S
  C --> CM["CommunityMap popup"] --> S
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Keep `Socials` props and add an adapter wrapper
  • ➕ Avoids touching all call sites immediately
  • ➕ Supports incremental migration per consumer
  • ➖ Prolongs the 22-prop surface area and duplication
  • ➖ Still needs a canonical extraction point somewhere
2. Make `Socials` data-driven via a key/metadata registry + loop
  • ➕ Removes per-key template conditionals and repeated sanitization lines
  • ➕ Adding new contact keys becomes configuration-only
  • ➖ Higher behavioral risk (ordering, layout, edge-case URLs)
  • ➖ Larger refactor than necessary for this slice; harder to review safely
3. Use `{#key data.id}` to reset all AreaPage state on navigation
  • ➕ Eliminates manual reset logic and stale-state risk
  • ➕ Simplifies mental model for navigation
  • ➖ Regresses AreaMap reuse (style/tiles refetch) and animation behavior
  • ➖ Conflicts with the stated performance/UX constraint

Recommendation: The PR’s approach is the best trade-off: a shared extractContacts plus a single contacts prop removes broad duplication with minimal behavioral change, and extracting AreaHeader materially reduces AreaPage complexity while fixing a real stale-state bug on client-side navigation. Avoiding a keyed teardown is appropriate given the AreaMap reuse constraint; manual resets are now limited to async machinery (taggers fetch + containment sweep).

Files changed (7) +249 / -444

Refactor (7) +249 / -444
Socials.svelteReplace 22 social/contact props with a single 'contacts' object +30/-22

Replace 22 social/contact props with a single 'contacts' object

• Changes the public API to 'contacts: AreaContacts' and destructures it reactively into the existing per-key variables. Keeps the existing per-key sanitization and rendering logic, but moves the fan-out to inside the component.

src/components/Socials.svelte

AreaHeader.svelteAdd derived-only AreaHeader extracted from AreaPage +135/-0

Add derived-only AreaHeader extracted from AreaPage

• Introduces an 'AreaHeader' component that derives avatar, description, org/sponsor, verification badges, socials presence, and tipping target directly from SSR 'data'. Exposes 'onVerifyClick' so AreaPage can handle section focus + smooth scroll to the maintain form.

src/components/area/AreaHeader.svelte

AreaPage.svelteDelegate header to AreaHeader; simplify sections; refine async gating +60/-311

Delegate header to AreaHeader; simplify sections; refine async gating

• Replaces enum + multiple mapping records with a single 'SECTIONS' array and a typed 'Section' union, and updates navigation logic accordingly. Removes imperative header initialization ('initializeData'/'dataInitialized'), fixes stale 'alias/name' by deriving them reactively, and introduces 'sweepDone' so merchant highlights gate on sweep completion while maintain/issues and activity initialization are handled explicitly.

src/components/area/AreaPage.svelte

contacts.tsIntroduce shared 'extractContacts(tags)' helper +16/-0

Introduce shared 'extractContacts(tags)' helper

• Adds a small utility that lifts the known 'contact:*' tag set into a typed 'AreaContacts' object using 'AREA_CONTACT_KEYS'. Centralizes contact extraction for SSR and client consumers.

src/lib/area/contacts.ts

areaSectionLoad.tsUse shared contacts helper when building SSR AreaPageProps +2/-20

Use shared contacts helper when building SSR AreaPageProps

• Removes the inline 'extractContacts' implementation and imports it from '$lib/area/contacts'. Ensures 'AreaPageProps.contacts' is consistently populated from tags across routes/sections.

src/lib/areaSectionLoad.ts

CommunityCard.svelteReplace per-key contact locals with extracted contacts object +4/-67

Replace per-key contact locals with extracted contacts object

• Computes 'contacts = extractContacts(tags)' and 'hasContact' via object key count, removing ~22 reactive assignments. Updates the Socials call site to pass '{contacts}' instead of many individual props.

src/routes/communities/[section]/components/CommunityCard.svelte

+page.sveltePass extracted contacts into Socials in community map popups +2/-24

Pass extracted contacts into Socials in community map popups

• Updates the imperative Socials instantiation for popups to use 'props: { contacts: extractContacts(community.tags) }'. Eliminates duplicated 'contact:*' prop wiring while preserving popup behavior.

src/routes/communities/map/+page.svelte

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Refactors the community/country area pages by centralizing contact:* tag extraction into a shared helper and extracting the header portion of AreaPage into a dedicated AreaHeader component, reducing prop fan-out and improving correctness across client-side area navigations.

Changes:

  • Introduce $lib/area/contacts.ts and update consumers to pass a single typed contacts object.
  • Update Socials to accept { contacts } instead of 22 named props.
  • Extract the profile/header markup into AreaHeader.svelte and simplify AreaPage section wiring/state resets.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/routes/communities/map/+page.svelte Uses extractContacts() when instantiating Socials in map popups.
src/routes/communities/[section]/components/CommunityCard.svelte Collapses many contact:* locals into a single contacts object passed to Socials.
src/lib/areaSectionLoad.ts Uses shared extractContacts() to populate AreaPageProps.contacts.
src/lib/area/contacts.ts New shared helper to lift contact:* tags into typed AreaContacts.
src/components/Socials.svelte Refactors component API to accept contacts object and destructure internally.
src/components/area/AreaPage.svelte Extracts header into AreaHeader, simplifies sections, and adjusts sweep gating.
src/components/area/AreaHeader.svelte New extracted header component deriving all display state from SSR data.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/components/Socials.svelte
Comment thread src/components/area/AreaPage.svelte Outdated
@qodo-code-review

qodo-code-review Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Undeclared website/email variables ✓ Resolved 📘 Rule violation ≡ Correctness
Description
Socials.svelte now destructures contacts into identifiers like website/email without
declaring them, which will cause TypeScript/Svelte compilation errors. This breaks the requirement
that pnpm run check must pass.
Code

src/components/Socials.svelte[R9-12]

+$: ({
+	website,
+	email,
+	phone,
Evidence
PR Compliance ID 3 requires TypeScript/Svelte checking to pass. The added reactive statement assigns
website, email, etc. from contacts, but those identifiers are not declared anywhere in the
updated file, which will trigger TypeScript/Svelte compile errors.

CLAUDE.md: TypeScript/Svelte Type Checking Must Pass
src/components/Socials.svelte[2-38]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`Socials.svelte` assigns to `website`, `email`, etc. via a reactive destructuring assignment, but those identifiers are no longer declared after removing the 22 `export let ...` props. In module/TypeScript scope this is a compile-time error (`Cannot find name ...`) and will fail Svelte/TS checking.

## Issue Context
The PR refactors `Socials` to accept a single `contacts: AreaContacts` prop, but the internal per-key variables still need to exist (either as `let` variables assigned from `contacts`, or by rewriting the template/derivations to read from `contacts` directly).

## Fix Focus Areas
- src/components/Socials.svelte[2-38]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Stale IssuesTable rows 🐞 Bug ≡ Correctness
Description
IssuesTable initializes its TanStack table from a one-time snapshot of issues and never
rebuilds/updates it when the issues prop changes. With AreaPage now always passing
loading={false}, navigating between areas while staying on the /maintain section can show the
previous area’s issue rows while the count uses the new issues.length.
Code

src/components/area/AreaPage.svelte[R278-282]

		<IssuesTable
			title={$_('area.taggingIssues', { values: { name: name || $_('area.defaultName') } })}
			{issues}
-			loading={!dataInitialized}
+			loading={false}
		/>
Evidence
The new loading={false} means IssuesTable will render immediately and stay in the non-loading
branch. IssuesTable only calls renderTable() once (when tableRendered is false) and
renderTable() snapshots issues into data, so later issues updates won’t update the TanStack
table, even though the header count is derived directly from issues.length.

src/components/area/AreaPage.svelte[277-282]
src/components/area/AreaPage.svelte[203-207]
src/components/IssuesTable.svelte[58-86]
src/components/IssuesTable.svelte[212-213]
src/components/IssuesTable.svelte[233-235]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`IssuesTable` creates its TanStack table once (guarded by `tableRendered`) using `issues.map(...)`, but it has no mechanism to update/recreate that table when the `issues` prop changes. This can leave stale rows visible after client-side navigation that reuses the component instance.

## Issue Context
- `AreaPage` now renders `<IssuesTable ... loading={false} />`, which causes the table to be constructed immediately and removes any chance of using `loading` transitions to force a rebuild.
- `AreaPage` updates `issues` reactively from `data`, so `issues` can change without `IssuesTable` being remounted.

## Fix Focus Areas
- src/components/IssuesTable.svelte[58-86]
- src/components/IssuesTable.svelte[212-213]
- src/components/area/AreaPage.svelte[277-282]

## Suggested fix approaches (pick one)
1) **Update the table’s data reactively** inside `IssuesTable` whenever `issues` changes (preferred):
  - Keep `options` / `table` instances stable, but update `options` with the newly mapped `data`.
  - Or, reset `tableRendered=false` when `issues` identity changes (track `lastIssues` to avoid loops), so `renderTable()` reruns.

2) **Force a remount of IssuesTable only** from `AreaPage`:
  - Wrap IssuesTable in a `{#key data.id}` block (or `{#key data.numericId}`) so it recreates when the area changes, without keying the entire page/map.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread src/components/Socials.svelte Outdated
Comment thread src/components/area/AreaPage.svelte
Base automatically changed from refactor/places-in-area to main August 4, 2026 14:57
escapedcat and others added 4 commits August 4, 2026 16:58
extractContacts moves to $lib/area/contacts, shared by the SSR bundle
and both client consumers. Socials' interface collapses from 22 named
props to one AreaContacts object (its internals and sanitization keep
their per-key names); the communities-map popup and CommunityCard drop
their own 22-line contact:* fan-outs for the same helper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AreaPage sheds its header half into AreaHeader — a prop-in/markup-out
component whose state fully DERIVES from the SSR bundle: no imperative
init, no reset lifecycle, an area navigation swaps data and every value
follows. That deletes initializeData wholesale, the 22 contact locals,
and most of the manual reset block (only the async machinery — taggers
fetch and containment sweep — still needs explicit resets). The three
parallel section Records collapse into one SECTIONS array whose id is
the route slug is the i18n key suffix. Also fixes a latent staleness
bug: alias/name were consts frozen at first mount while the component
instance is reused across area navigations.

Deliberately NOT done, per the issue's re-review: no {#key} boundary
(AreaMap's instance-reuse path — setData + animated fitBounds across
area navs — must survive; a keyed teardown would refetch style and
tiles), and reportsSync stays page-level because the merchants section
consumes the report-fed AreaMap stars (#1186).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot follow-up catching a #1210-pruning interaction: the link's
legacy in-place section switch rendered maintain with an empty issues
table (issues arrive only with the maintain route's own load) and left
the URL on the old section. The anchor now navigates natively — the
maintain load fetches the issues, the hash scrolls to the form, and
AreaHeader's onVerifyClick prop disappears along with the page-side
handler and its manual activeSection override.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rop change #1176

Two qodo follow-ups. Socials drops the reactive destructure entirely —
the sanitized derivations and template read contacts.* directly, so
there's no Svelte-4 auto-declaration magic for tooling (or the future
runes migration) to trip over. IssuesTable resets its render guard when
the issues prop identity changes: the TanStack table snapshots issues at
build time, and a client-side area navigation reusing the component
previously left the OLD area's rows under the NEW area's count — a
pre-existing bug this stack's loading={false} merely unmasked.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@escapedcat
escapedcat force-pushed the refactor/area-page-split branch from 8eca3f9 to 0d62516 Compare August 4, 2026 15:00
@netlify

netlify Bot commented Aug 4, 2026

Copy link
Copy Markdown

Deploy Preview for btcmap ready!

Name Link
🔨 Latest commit 0d62516
🔍 Latest deploy log https://app.netlify.com/projects/btcmap/deploys/6a71fe8319581a0008112f35
😎 Deploy Preview https://deploy-preview-1212--btcmap.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 44 (🔴 down 23 from production)
Accessibility: 97 (no change from production)
Best Practices: 92 (🔴 down 8 from production)
SEO: 96 (no change from production)
PWA: 90 (no change from production)
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@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 `@src/components/area/AreaPage.svelte`:
- Around line 163-165: The reactive block gating runContainmentSweep uses
`$places.length` (and similarly `$reports.length` in the areaReports logic) as a
proxy for "sync complete," but this is indistinguishable from a valid empty
result, causing filteredPlaces/sweepDone and AreaStats to remain stuck in a
loading state when the collection is legitimately empty. Replace the
length-based checks with an explicit sync/loading completion flag (e.g., a
status field from the data/store indicating fetch completion) to gate the
`sweptAreaId !== data.id` condition. Ensure that when the underlying data sync
completes with an empty result, the code still calls runContainmentSweep (or the
equivalent reporting logic) to publish `[]` and set `sweepDone = true`, rather
than skipping it due to zero length. Apply the same fix to the analogous
areaReports block referenced at the other line ranges.
🪄 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: dc84b7cb-8a16-467b-9771-6994a88d462b

📥 Commits

Reviewing files that changed from the base of the PR and between 73a2529 and 0d62516.

📒 Files selected for processing (8)
  • src/components/IssuesTable.svelte
  • src/components/Socials.svelte
  • src/components/area/AreaHeader.svelte
  • src/components/area/AreaPage.svelte
  • src/lib/area/contacts.ts
  • src/lib/areaSectionLoad.ts
  • src/routes/communities/[section]/components/CommunityCard.svelte
  • src/routes/communities/map/+page.svelte

Comment thread src/components/area/AreaPage.svelte
@escapedcat
escapedcat merged commit 4ecd354 into main Aug 4, 2026
12 checks passed
@escapedcat
escapedcat deleted the refactor/area-page-split branch August 4, 2026 15:13
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