Skip to content

feat: volunteer work section with timeline integration#101

Open
rorar wants to merge 17 commits intovincentmakes:mainfrom
rorar:main
Open

feat: volunteer work section with timeline integration#101
rorar wants to merge 17 commits intovincentmakes:mainfrom
rorar:main

Conversation

@rorar
Copy link
Copy Markdown

@rorar rorar commented Apr 14, 2026

Summary

  • Volunteer work section with roles, timeline integration, and Schema.org markup
  • XSS, path traversal, input validation, and performance fixes (P0/P1)
  • Dataset authorization fix (S12), Schema.org on volunteer cards (CQ4), sort stability (CQ5)
  • i18n support across all 8 languages
  • Allium specs for CV Manager domain model
  • Import utilities with JSON_SAFE_PARSE and sanitization

Test plan

  • All 142 tests pass
  • Manual testing of volunteer work CRUD in admin UI
  • Verified public timeline includes volunteer roles
  • Verified Schema.org markup in public view

rorar and others added 17 commits April 13, 2026 20:28
The public page was passing data.volunteer (undefined) instead of
data.volunteer_work, causing the volunteer section to render empty
when the timeline fallback path was used.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- rules.allium: add volunteer_work to SaveDataset, LoadDataset, and
  DatasetAutoSave ensures clauses; active_volunteer_work was declared
  in the given block but missing from all three rules
- surfaces.allium: add dataset.volunteer_work where visible = true
  to PublicCV exposes block

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Security:
- Tracking code XSS: add allowlist validation at PUT /api/settings/:key
  and defensive script-tag check in getTrackingCode()
- Volunteer role title XSS: stripHtml() before storing roles
- Volunteer org/desc XSS: stripHtml() on organization and description
- File upload path traversal: strict regex + path.resolve() check on
  all three logo PUT endpoints (experiences, certifications, education)
- Dataset slug XSS: escapeJsString() on all DATASET_SLUG assignments
- performImport now sanitizes imported volunteer data

Input validation:
- MAX_ORGANIZATION_LENGTH=200, MAX_DESCRIPTION_LENGTH=2000,
  MAX_ROLE_TITLE_LENGTH=100, MAX_ROLES_COUNT=20
- Organization required; length and array limits enforced on POST/PUT

Performance:
- 10 composite indexes on (visible, sort_order) for all tables
- JSON_SAFE_PARSE used consistently at all 9 volunteer roles locations
- Added country_code column to volunteer_work table

Bug fixes:
- Volunteer DELETE now returns 404 if record not found
- ATS PDF skips empty role titles
- Static export key renamed to volunteer_work for consistency

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ine sort stability

- S12: /v/:slug now only serves datasets with is_public=1 (default
  datasets are served exclusively at /)
- CQ4: Volunteer cards include itemscope itemtype="https://schema.org/OrganizationRole"
  with itemprop="organization" and itemprop="description" for SEO/ATS
- CQ5: Timeline sort now uses volunteer_work.sort_order as secondary
  tiebreaker when start_date ties exist, ensuring stable ordering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes missing translation key used in Volunteer Work form.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces hardcoded "Present" placeholder with i18n function in
volunteer role date inputs (admin.js).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vincentmakes
Copy link
Copy Markdown
Owner

vincentmakes commented Apr 14, 2026

Thanks @rorar and the security hardening is appreciated.
That said, the main issue is architectural: we already have a first-class extension mechanism for exactly this use case. docs/guide/custom-sections.md explicitly cites volunteer work as a fit for the Vertical List layout, and additional-experiences is very close to what you’ve built. The only genuinely new requirement is multiple roles per organization — that should be solved by extending additional-experiences (optional roles array) or adding a volunteer-roles layout_type to custom_sections, not by introducing a new table, new endpoints, new section enum entry, new migrations, and new ATS/PDF code. Could you rework the feature on top of the custom-section architecture? If there’s a reason that route is insufficient, please make the case in the PR description.
On top of that, a number of repo conventions haven’t been followed. Please re-read CLAUDE.md and CONTRIBUTING.md carefully before the next iteration as both spell these out:
• No version bump (package.json, package-lock.json, version.json)
• No CHANGELOG.md entry
• PR batches unrelated changes (feature + security + refactor + specs)
• No UI screenshots
• Security fixes referenced only by codename, no root-cause explanation
• User documentation missing (docs/guide/sections.md out-of-date, no volunteer.md page, no translated variants, README.md not updated)
• Feature is on-by-default for all upgrading users (should be opt-in via migration)
• Hardcoded English fallbacks in JS (t('...') || 'English') and mistranslated action.add_volunteer in 6 locales
• Unrelated i18n churn (casing changes, blank-line removals, stray JSON whitespace)
• serveDatasetData may 404 the default dataset via /v/{slug}
• performImport changes import semantics for partial payloads
• Dead country_code migration on volunteer_work
• Timeline dot anchor visual change bundled silently
• Unexplained specs/*.allium files + tooling .gitignore entries
Suggested split once the architecture is sorted:
• PR A: security fixes — PATCH
• PR B: refactors (timeline/import) + indexes — PATCH
• PR C: volunteer feature on the custom-section architecture + docs — MINOR
• PR D: spec files / tooling (or keep local)

Thanks again for the work.

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