Skip to content

Release: v0.1.0#137

Merged
themightychris merged 30 commits into
mainfrom
develop
Jun 30, 2026
Merged

Release: v0.1.0#137
themightychris merged 30 commits into
mainfrom
develop

Conversation

@github-actions

@github-actions github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown

First tagged release of the modernized platform — the leadership-feedback UI cluster, person lifecycle + admin tooling, legacy avatar import, and the gitsheets 1.4.1 boot-memory fix.

Improvements

Technical

themightychris and others added 2 commits June 26, 2026 11:19
The horizontal logo sat at h-8 (32px) in the h-14 (56px) bar, leaving ~12px of
dead space above/below. Bump to h-12 (48px) so it fills ~86% of the bar height
with a 4px gap top and bottom — a fuller, more present lockup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(web): enlarge header logo to fill the navbar
@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown
Author

Changelog

- fix(web): enlarge header logo to fill the navbar [#136] @themightychris
- feat(web): add Join/Leave project buttons (#113) [#138] @themightychris
- feat(web): self "Manage account" link on PersonDetail (#113) [#139] @themightychris
- feat(web): ProjectDetail soft-delete banner for staff (#113) [#140] @themightychris
- feat(blog): excerpt fallback + tag chips (#113) [#141] @themightychris
- feat(web): ProjectDetail "More ▾" actions dropdown (#113) [#142] @themightychris
- feat(import): import legacy person avatars (#130) [#143] @themightychris
- feat(person): deactivate / reactivate / purge (#129) [#144] @themightychris
- chore(deps): bump gitsheets to 1.4.1 (TOML parser memory fix, #132) [#145] @themightychris
- chore(deploy): lower memory limits after gitsheets 1.4.1 heap fix [#146] @themightychris
- feat(person): admin account-level endpoint (#33) [#147] @themightychris
- fix: resolve spec-drift audit findings (code + specs) [#148] @themightychris

themightychris and others added 27 commits June 26, 2026 13:21
ProjectDetail declared "Join Project" / "Leave project" actions in the spec
(project-detail.md) and the API endpoints existed, but the SPA never rendered
the buttons. Add them to the sidebar:

- Join Project — signed-in users who aren't members (POST .../members/join)
- Leave project — members, except a sole maintainer who must transfer the
  role first (POST .../members/leave); shows an explanatory hint instead.

Membership is computed client-side from the members list + the signed-in user
(the project response carries no per-viewer flag). Added api client join/leave
methods and ProjectDetail tests for both states.

First slice of the #113 UI-gaps umbrella. The soft-delete banner (the other
half of the ProjectDetail pairing) is deferred — it needs the project response
to expose `deletedAt`, a small API/serializer change tracked under #113.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eave

feat(web): add Join/Leave project buttons (#113)
person-detail.md's sidebar declares a self-only "Manage account" link to
/account; the SPA didn't render it. Add it to the sidebar, gated on the viewer
being the profile owner (useAuth slug match). Tests cover self (link present,
href=/account) and anonymous (absent).

Note: the other two items in this #113 batch were already implemented since the
2026-05-30 audit — PeopleIndex's sort dropdown (PeopleIndex.tsx) and
HelpWantedIndex's "Post a role" button — so they need no change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(web): self "Manage account" link on PersonDetail (#113)
project-detail.md declares a staff-only banner across the top of a soft-deleted
project with a Restore action; the SPA never rendered it because the project
response didn't carry deletedAt.

- Expose `deletedAt` on the project detail response (serializer + spec
  api/projects.md). Null for active projects; non-null only when staff fetch a
  soft-deleted one (non-staff get 404).
- Render the yellow banner with a Restore button (reuses the existing action
  runner + project refetch). Gated on deletedAt + staff accountLevel.
- Tests: banner shows for staff + deleted, absent for active.

The "More ▼" header dropdown (the other half of this ProjectDetail pairing in
#113) is a separate follow-up — it's a pure UI refactor with no API dependency.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-more

feat(web): ProjectDetail soft-delete banner for staff (#113)
Two blog UI gaps from the spec-drift audit:

- BlogIndex (blog-index.md): when `summary` is null, fall back to the first
  paragraph of `bodyHtml` truncated to ~280 chars (plain-text, derived
  client-side from the already-sanitized HTML).
- BlogDetail (blog-detail.md): render the post's tags as chips in the footer
  linking to `/blog?tag=<handle>`. The blog response didn't carry tags, so the
  serializer now resolves them (tag-assignments where taggableType=blog_post)
  and includes a `tags` field (spec api/blog.md updated). Web type + UI added.

Tests: BlogIndex excerpt fallback; BlogDetail chips present/absent; API blog
suite still green (tags in response).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per project-detail.md, the header keeps "Edit Project" as the primary button
and moves the secondary actions into a "More ▾" dropdown: Add Member, Log Buzz,
Post Update, Post Help-Wanted Role, Manage Members, and (admin) Delete Project
behind a confirm dialog. The previously-contextual section buttons (Post Update,
Log Buzz, Post new role) are removed now that they live in the header dropdown —
single source per the spec.

Delete soft-deletes via the existing endpoint and refetches, so the soft-delete
banner (from the prior PR) appears for staff immediately. Tests: dropdown
trigger present for management perms, absent for anonymous.

Completes the #113 UI-gaps umbrella.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(blog): excerpt fallback + tag chips (#113)
…ropdown

feat(web): ProjectDetail "More ▾" actions dropdown (#113)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Legacy users showed initials where their codeforphilly.org photo used to be —
the importer brought blog media but never person avatars. A spike found the
source: person.PrimaryPhotoID → GET /media/<id>. (Projects have no image field
in laddr, so this is avatars only.)

RawPersonSchema now parses PrimaryPhotoID. For each person with one, the
importer fetches /media/<id>, runs it through the existing processAvatar
(square original + 128px thumb), stores both as gitsheets attachments
(avatar.jpg + avatar-128.jpg), and sets avatarKey — the same convention as the
avatar-upload route. Reuses the blog-media machinery (fetchMediaBytes +
BlobObject.write + setAttachments), concurrency 4, failures non-fatal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two removal verbs: deactivate (soft, self-service, reversible, login not
blocked, "Deactivated user" placeholder on references) and purge (admin-only
cascading hard delete, git-revertable). Authz + endpoints + placeholder shape.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- API: POST /api/people/:slug/{deactivate,reactivate} (self | staff) and
  /purge (administrator), via the write mutex.
- Read: people.get returns a deactivated person only to staff or self (for
  reactivation); lists exclude deactivated for non-staff; serializePersonAvatar
  (+ author/member serializers) emits a "Deactivated user" placeholder.
- Purge cascades: person + memberships + help-wanted-interest + person
  tag-assignments + authored updates/buzz/blog-posts, in one commit.
- Web: /account self deactivate/reactivate; admin Danger Zone; placeholder
  rendering.

Implements specs/behaviors/person-lifecycle.md + api/people.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
API guard + cascade tests (14) and web tests for self-deactivate and the
deactivated-reference placeholder. Fixes the draft test's mintCookies helper,
which ignored its level arg so staff/admin callers authenticated as plain users
(spurious 403s).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(import): import legacy person avatars (#130)
feat(person): deactivate / reactivate / purge (#129)
Command:
  npm install gitsheets@^1.4.1 -w apps/api

Picks up gitsheets 1.4.1, which switches the TOML record parser from
@iarna/toml to smol-toml (JarvusInnovations/gitsheets#197). This fixes
the #132 boot-heap blowup at its root: @iarna's parser pinned ~12x each
record's source in V8 sliced/cons-strings.

Measured end-to-end against the full `published` import via the boot
path:
  retained heap     581 MB -> 88 MB  (6.6x)
  transient peak    532 MB -> 71 MB  (7.5x)
  rss               806 MB -> 292 MB

type-check + lint clean; api 412/412 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
chore(deps): bump gitsheets to 1.4.1 (TOML parser memory fix, #132)
gitsheets 1.4.1 (smol-toml parser, #132) cut the boot footprint
dramatically. Measured against the full `published` import:
  heap     >500 MB -> ~123 MB
  RSS         ~806 MB (incident) -> ~321 MB

Resize from the leak-era ceilings to the real working set, with margin:
  NODE_OPTIONS --max-old-space-size  2048 -> 512   (~4x boot heap)
  requests.memory                    1Gi  -> 512Mi (> ~321Mi steady RSS)
  limits.memory                      2.5Gi -> 1Gi  (~3x steady RSS)

Hands the ~3.9Gi nodes ~1.5Gi back vs the old 2.5Gi limit, removing the
node-starvation risk that drove the earlier NodeNotReady.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
chore(deploy): lower memory limits after gitsheets 1.4.1 heap fix
POST /api/people/:slug/account-level — administrator-only, the sole path
for changing accountLevel (never via the generic PATCH), so the privilege
change is explicit and audit-logged.

- Write service setAccountLevel: requireAuth('administrator'); idempotent
  no-op when unchanged; last-administrator guard (refuse to demote the only
  administrator → 422, prevents lock-out).
- Route sets Action: account-level.change + Previous-Account-Level /
  New-Account-Level audit trailers; returns the updated person (200).
- Spec promoted from a deferred stub to a full section. Corrected the
  bad-body error to 422 (this app maps Fastify schema-validation errors to
  422, not 400) — caught by the test.

Tests (10): authz matrix, invalid level, promote/demote, idempotent no-op,
last-admin 422, demote-with-second-admin, and the audit-trail trailers.
type-check + lint clean; people suites 24/24.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(person): admin account-level endpoint (#33)
From the spec-drift audit:

- Remove the legacy DELETE /api/people/:slug route + softDelete service
  method — an undocumented admin-only soft-delete running parallel to the
  canonical self|staff POST /deactivate (diverging authz). Superseded by
  deactivate/reactivate/purge.
- commit-meta: emit the Response-Message trailer (HTTP reason phrase) that
  specs/behaviors/storage.md#commit-message-shape requires; only
  Response-Code was being written.
- PATCH /api/people/:slug: enumerate editable fields + additionalProperties:
  false so privileged fields (accountLevel) are rejected (422) instead of
  silently ignored — hardens the dedicated-endpoint rule.

type-check + lint clean; people + write-api suites 52/52.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- storage.md: resolve the public/private contradiction. Ground truth:
  codeforphilly-data is a PRIVATE GitHub repo today holding public-by-design
  content. Corrected the table claim that it "contains emails, real names,
  IPs" (which contradicted the redaction section — PII lives in the private
  store). Fixed "publicly cloneable" framing, the scrub-data.ts path, and
  clarified commit bodies carry only the caller summary (request-body
  redaction is moot).
- people.md: document slackHandle + deletedAt in the Person response shape
  (deletedAt gated to self/staff); clarify ?accountLevel= returns empty 200
  to non-staff; point the PATCH note at the dedicated account-level endpoint.
- legacy-id-mapping.md: tighten the byLegacyId claim — runtime indices exist
  only for people/projects/blog-posts; the other sheets carry legacyId for
  import idempotence only.
- conventions.md: mark Idempotency-Key as built-but-not-yet-wired (status).

plans/spec-drift-fixes.md records real findings, fixes, and the verified
false positives (swapPublic, perPage 400-vs-422, the account-level trailer
"race") that were checked rather than blindly "fixed".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix: resolve spec-drift audit findings (code + specs)
@themightychris themightychris merged commit 0d59c09 into main Jun 30, 2026
5 checks passed
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