Skip to content

Contract release: @civfix/shared 0.58.0 - #54

Closed
theobong wants to merge 10 commits into
chore/app-campaign-13-perffrom
chore/app-campaign-14-contract
Closed

theobong wants to merge 10 commits into
chore/app-campaign-13-perffrom
chore/app-campaign-14-contract

Conversation

@theobong

@theobong theobong commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

What changed

The @civfix/shared 0.58.0 contract release. It carries every campaign change to the published package since 0.57.0; the full list is in the changeset (packages/shared/CHANGELOG.md, 0.58.0) and DECISIONS §58.

  • Removed: 20 value exports and one type that no consumer uses (checked across civfix-backend, civfix-admin, civfix-govt-web and their open PRs).
  • Added:
    • the input limits the schemas already enforce, as named constants that ui, web and mobile now use instead of copies;
    • the optional fields the backend and admin sessions asked for;
    • shared isUuid, time units and stripTrailingSlashes;
    • the error, formatter, host-model and timeout helpers from PRs 12 and 13.
  • Fixed:
    • AppError subclasses keep their prototype;
    • the api client rejects a non-JSON success body instead of resolving undefined;
    • calendar-file link and organizer values follow RFC 5545 and must be safe https links;
    • the unsafe-link check no longer flags ordinary text such as "Questions about: parking";
    • feed cursors are case-insensitive;
    • the fakes are more honest.
  • Version: @civfix/shared 0.58.0 (a minor, because it removes exports), via changesets.

Before you start

Verify

Nothing should look different. The checks cover the forms whose length limits now come from the contract.

[Web] [Mobile] Length limits

  1. In "Settings", edit "Display name" and your username, and paste text longer than the limit. Expect: the field stops at the same length as before (display name 80, username 20).
  2. In a chat, create a poll with 10 options and try an 11th. Expect: the same limits and messages as before.
  3. Start a report and fill the title and details to the limit. Expect: the same caps (title 120, details 2000) and up to 5 photos.
  4. Host an event and fill the title and description to the limit. Expect: the same caps (120 and 2000).

[Web] Host console

  1. In the org settings, fill the verification note and the social handles to the limit. Expect: the same caps as before (1000, 30).
  2. In "Signup page", fill each block's fields and add agenda, host, FAQ and sponsor rows to the limit. Expect: the same caps and row counts as before.
  3. In the signup questions, fill a consent text and an option value to the limit. Expect: the same caps.

[Web] [Mobile] Calendar file

  1. On a signup page, choose "Add to calendar". Expect: the calendar file opens with the event's link and organizer as before. A link with commas or semicolons is no longer mangled.

Regression

Sign-in codes: [Web] [Mobile]

  1. Sign in with an email code, and on mobile open the code screen. Expect: 6 code cells as before; the reviewer login still accepts its long code.

Host pages with links in text: [Web]

  1. Publish a broadcast whose text includes "Questions about: parking" and a normal https:// link. Expect: it is accepted and the link works. Text with javascript: (with or without a space after it) is still refused.

Findings addressed

  • 40 PR 14 ledger rows fixed: removals, contract constants, contract bug rows and additive handoff requests.
  • 12 skipped, each with its reason in the ledger:
    • kept exports that the backend or admin use;
    • type-only removals (the contract keeps its types);
    • an enum a backend switch depends on;
    • six product or design decisions (D38 to D43).
  • 2 needed no action.

Decisions for the reviewer

  • Removals: the name list is in the changeset. Each name was checked against civfix-backend services/api and services/media-worker, civfix-admin apps/admin, civfix-govt-web, civfix-govt-shared, and the open backend and admin PRs.
    • Kept: DEFAULT_DURATION_MS (the backend imports it), and DiscoveryReviewStatusSchema and DISCOVERY_REVIEW_STATUS_LABELS (admin is adopting them).
    • WebReportType loses its placeholder gov routing field; admin reads only id, category and label.
  • Adoption order for the new optional fields:
    • Most of the new response fields sit on .strict() DTOs, so the order is: publish, then admin adopts and ships, then the backend emits.
    • The new strict request fields (flagged, the walk-up idempotencyKey) go the other way: the backend implements them first (a manifest bump alone would parse flagged and keep toggling), then clients send them.
    • Both handoff files spell this out.
  • Behaviour changes that reach consumers:
    • the typed client rejects a non-JSON 2xx body (every backend 2xx sends JSON or 204);
    • ICS URI values are no longer TEXT-escaped, and an unsafe URL is dropped;
    • the unsafe-scheme check still refuses an executable scheme at a token start, even after whitespace (javascript: alert(1)); only about: used as prose and scheme words inside an https path are no longer refused;
    • the score cursor id is lowercased (the backend already normalizes it, so its normalizeScoreCursor can go).
  • Deferred: these would change existing behaviour or need a decision, so they are listed in DECISIONS §58 and the pending decisions, not made:
    • CSRF on media upload;
    • an oauthNonce registry entry;
    • a stricter SuggestContact request;
    • enum growth;
    • a nullable MediaDTO.url;
    • strict response DTOs.
  • civfix-govt-web stays on ^0.24.2 by decision (tokens only).

User-visible copy changes

None.

Tests changed

  • New:
    • contract limit boundary tests: each max parses, max+1 fails, and HANDLE_REGEX is unchanged;
    • handoff additions (old payloads still parse; new fields parse);
    • units and ids;
    • a failing-before test for each behaviour fix: AppError subclasses, non-JSON success body, ICS escaping and validation, unsafe-scheme positions across 7 schemes × 20 openers × 5 payloads, score cursor, fakes, geocode onError.
  • Removed with removed code: tests of comparePrecision, API_VERSIONS, registrationSeries, hourlySeries, repeatAttendanceRate, webReportTypeToCategory, EventInsightsSchema, REPORT_VOLUNTEER_HOURS, ipLocate, mixWithWhite and chipPairPasses.
  • Rewritten with the same guarantee:
    • the cumulative-series invariants go through seriesClosure;
    • the forward-template privacy tests use FORWARD_TEMPLATE_VARIABLES;
    • response warnings reset with vi.resetModules;
    • the leaderboard clamp test is now behavioural, and it and the turnstile pins sit in their own test: commit.

Verification

  • Node v22.23.2, pnpm 9.12.0, in the PR worktree:
    • pnpm typecheck, pnpm lint, pnpm i18n:check, pnpm build and pnpm doctor are green.
    • Tests: shared 1352/1352, ui 6561/6561, mobile 698 pass / 0 fail, web 1142/1143.
    • The one web failure is the known load-only flake that PR 2 (Test infrastructure and characterization safety net #41) fixes; it passes alone.
    • node scripts/check-shared-version.mjs: 0.58.0 is not on the registry yet.
    • The web export's pages and every page's title, OG and Twitter tags are identical to the previous PR's.
  • Two adversarial reviews:
    • contract compatibility and security: clean.
      • A structural diff of all 333 registry entries against the 0.57.0 tag shows only the documented optional additions.
      • The runtime export surface loses exactly the documented names.
      • No removed name is used on backend or admin main, or on any open PR head.
      • The backend's ICS output is byte-identical.
    • Its two should-fixes are fixed:
      • the scheme check keeps refusing javascript: after whitespace;
      • the adoption rule for strict request fields says "implemented".
    • App-side replacements, tests and docs: findings fixed.
      • the two remaining limit copies now come from shared, which adds CONTENT_REPORT_DETAILS_MAX;
      • the drift-adapted test edits are in their own test: commit;
      • the deferred lists are consistent.
  • CI runs only on pull requests into main, so it starts when this PR retargets after Measured performance: smaller bundles and fewer renders #53 merges.

Not covered

  • Consumers are not typechecked here against 0.58.0; the adoption PRs' CI is that check. If one fails because a consumer still uses a removed export, the fix is to restore the export in civfix-app, not to patch the consumer.
  • The admin screens that will use the new optional fields are admin's own follow-ups.

🤖 Generated with Claude Code

…nt rejects a non-JSON success body, safe ICS URIs, fewer false unsafe-scheme hits, lowercase score cursors, honest fakes, additive isErrorCode and geocode onError
…rt status buckets, and shared uuid, time unit and url helpers used by the app
…come from shared; docs list every deferred contract request
…e, and the adoption rule for strict request fields says implemented
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