116 Commits
Author SHA1 Message Date
Jules 8a70822731 refactor(funnel): single source of truth for funnel form options
* refactor(web): dedupe funnel form options across the 3 forms

`skills`, `chatService` and `country` were byte-identical in all three
form definitions. They move to `packages/funnel/src/form-options.ts` and
reach the pages through `AFFORM_OPTIONS`; the form files now describe
rendering only.

Lists verified ordered-identical to the originals, entry for entry.

Closes #140

* refactor(civi-crm): derive notion id/label maps from shared funnel options

`SKILLS_MAP`, `CHAT_SERVICE_MAP` and `COUNTRY_MAP` were a fourth copy of
the funnel option lists. They are now `toLabelMap()` over the shared
arrays, the way `HEAR_ABOUT_MAP` already was, so an id cannot mean one
thing in the dropdown and another in Notion.

All 275 derived entries verified identical to the previous literals, so
the Notion and n8n writes are unchanged. The one exception is country
1054, normalised to `Côte d'Ivoire` -- the spelling Notion already stored
and the one the other apostrophised country names use. The dropdown label
and the Ghost member note now match it.
2026-08-26 00:35:44 +09:00
Jules dfe8cf0451 fix(civi-crm): validate required funnel fields on afform-submit
* feat(funnel): add per-form required field list

Single source for which funnel answers are required, plus the check the
intake endpoint runs a submission against.

* refactor(web): build the funnel form schema from the shared list

Form definitions no longer carry a `required` flag, they describe
rendering only. The three pages pass REQUIRED_FIELDS_BY_FORM into
ConnectFormSection, which derives the zod schema and the required markers
from it.

* fix(civi-crm): validate required funnel fields on afform-submit

Only formName, the hear-about answer and the captcha were checked, so a
direct POST could create a record with no name, email or country. A
payload missing a required answer now gets a 400 naming the fields.

* docs(funnel): document the required-field validation
2026-08-24 23:24:13 +09:00
jinhojang6 7af2285712 fix(web): fail the build when the RFP source is unreachable
The sitemap fetched RFPs strictly while generateStaticParams and the RFP
listing used a lenient wrapper that degraded to an empty array. Under
`output: 'export'` there is no runtime fallback, so a GitHub hiccup during
generateStaticParams shipped a site whose RFP detail routes all 404 while
the sitemap still advertised them.

Every caller now uses the throwing fetch, so the page set and the sitemap
always agree. Transient failures (403/429/5xx and network errors) retry
with exponential backoff — including the directory listing, which was
single-shot — while a 404 falls straight through to the next fallback URL
instead of burning two pointless retries.
2026-08-20 23:10:38 +09:00
jinhojang6 e65454efc1 fix(web): harden sitemap RFP sourcing and breadcrumb names
Sitemap RFP entries now come from fetchGithubRfpsStrict, which throws when the Github listing or any individual RFP file cannot be read. Previously a failed fetch silently returned an empty list, so a Github outage shipped a sitemap with no RFP URLs while CI stayed green. The RFP pages keep the lenient loader and still degrade to an empty listing.

The static export smoke test derives RFP routes from the exported HTML instead of making a second Github call, which removes the build-time vs check-time race and makes the check fully offline.

Technology stack pages read the parent /technology-stack page copy for the parent breadcrumb name, instead of deriving it from the child page's own hero eyebrow.

Organization @id and url use the normalised site origin, and the structured data tests derive their base URL from siteConfig rather than hardcoding logos.co.
2026-08-20 23:10:38 +09:00
jinhojang6 5fcb7c5fa5 feat(web): add structured SEO data 2026-08-20 23:10:38 +09:00
jinhojang6 a7571936db fix(web): prevent ambiguous Umami events 2026-08-13 23:02:42 +09:00
Jules bc30c4cede refactor(civi-crm/web): drop CiviCRM integration
* refactor(civi-crm): remove case management pages and API

The Circle Case UI, its API routes and the CiviCRM query layer they used
are gone. Keeps the app shell out entirely: no pages remain, so the
layout, styles and UI dependencies go with them.

* refactor(civi-crm): drop CiviCRM write from the intake endpoint

Submissions now go to Notion and, for the steward form, n8n. The legacy
afformCircleContactForm name was CiviCRM-only and is no longer accepted;
the fields[] payload is ignored.

* chore(web): remove CiviCRM afform generator scripts

They fetched form definitions from the CiviCRM API, which is gone. The
generated files under lib/civicrm are hand-maintained from now on.

* refactor(web): trim funnel form definitions and move to lib/funnel-forms

Fields keep only what renders the form: formKey, label, inputType,
required, repeatable. The CiviCRM entity/join/fieldName plumbing and the
fields[] POST payload are gone, along with the unused circle contact
form.

country was marked required but exempt from validation through its
CiviCRM Address join, so it is now enforced like every other required
select.

* docs: rewrite civi-crm and funnel docs for the intake-only app

Drops the CiviCRM API guide and the case management architecture, and
updates the funnel reference to the Notion + n8n flow.
2026-08-13 22:44:23 +09:00
jinhojang6 1360bd70df feat: add roadmap page 2026-08-11 04:00:54 +09:00
jinhojang6 8f023f99d3 fix: restore Basecamp platform downloads 2026-08-06 01:18:47 +09:00
Jules cc621dcfb1 feat(web): forward submitted form data on newsletter signup
* chore(web): add a debug level to the logger

Logs in every build but tests, for output a deployed preview needs.

* feat(web): forward submitted form data on newsletter signup

Every field a form collects is sent to the subscribe endpoint, select
values as their labels rather than CiviCRM option ids. Logs the payload.

* docs(funnel): document the forwarded subscribe payload

* fix(web): build the funnel newsletter payload from answers only

Keeps the intake captcha token and field defs out of the newsletter
service whatever a caller puts in extraPayload.
2026-08-05 19:42:15 +09:00
jinhojang6 65146bdc95 fix: avoid Basecamp download redirect page 2026-07-31 01:51:19 +09:00
jinhojang6 7410b469c8 fix: resolve Basecamp downloads by platform 2026-07-31 01:51:19 +09:00
jinhojang6 4b0620faba fix: update movement issue links 2026-07-31 01:51:19 +09:00
Jules 96901178e0 feat(web): subscribe funnel opt-ins to newsletters
* refactor(funnel): move profile map to @repo/funnel

The profile label is now needed by apps/web too. Keeps it from drifting
from the Notion Profile select.

* feat(web): subscribe funnel opt-ins to newsletters

Each ticked checkbox subscribes the submitted email to its Ghost
newsletter and appends a note to the member profile. Fired after the
intake POST resolves, sequentially, and never surfaces as a form error.

* docs(funnel): document newsletter opt-ins
2026-07-30 21:31:50 +09:00
Jinho Jang 3e36fc9790 Increase small text to 12px (#114)
* fix: increase small text to 12px

* fix: use initiative CTA label

* refactor: use Tailwind text-xs utility

* fix: make homepage banner dismissible

* fix: update Discord links and banner text

* fix: refresh Discord invite links
2026-07-23 20:58:59 +09:00
jinhojang6 f83afc2062 fix: avoid duplicate umami events 2026-07-16 22:24:15 +09:00
jinhojang6 8c9a0ac8e7 feat: track get started card clicks 2026-07-16 22:24:15 +09:00
jinhojang6 1f4709aa40 feat: add operators sunset disclaimer 2026-07-13 22:49:37 +09:00
jinhojang6 599186db28 fix: improve operators legal markdown rendering 2026-07-13 22:49:37 +09:00
jinhojang6 5188f321f6 feat: add operators legal tabs 2026-07-13 22:49:37 +09:00
jinhojang6 f90761e19e fix: route lambda prize discord cta 2026-07-09 20:29:25 +09:00
jinhojang6 1f8da80d29 fix: route builder ctas to get started 2026-07-09 20:29:25 +09:00
Jules a7e5eedf35 feat: add "How did you first hear about Logos?" field to funnel forms
* feat: add @repo/funnel package for shared hear-about constants

Single source of truth for the "How did you first hear about Logos?"
question, options, and id -> label map used by apps/web and apps/civi-crm.

* feat: map hear-about answer to a Notion select on intake

Unknown option ids are dropped so tampering cannot create new select
options; field defs without a CiviCRM fieldName are skipped in
buildAfformValues so the Afform payload is unchanged.

* feat: add required hear-about field to the three funnel forms

Injected below the chat name/service row on coalition-partner,
activist-builder and activist-leader-steward. Web/Notion-only: defs
without a fieldName are filtered out of the CiviCRM fields payload.

* docs: document hear-about field in funnel reference

* refactor: extract resolveHearAboutLabel from Notion builder

Share the option-id -> label resolution so the route can reuse the
builder's exact accept/write predicate.

* fix: enforce required hear-about field server-side

Reject funnel submissions whose hear-about answer is missing or not a
known option id, instead of relying on client-side validation alone.
2026-07-08 19:39:42 +09:00
Jules 7d820fbfd3 chore(web): update movement page copy
* chore(web): sentence-case circle titles and resources copy

Applies movement copy review to shared circle content (also rendered on /circles): initiative card titles, resources section heading and description.

* chore(web): update movement page copy

Applies the movement site copy review: hero, intro heading/body, action cards, campaign, find, coalition, and builder sections.
2026-07-08 00:58:37 +09:00
jinhojang6 a54d6a2668 fix: update links and descriptions for Basecamp release v0.2.1 and Testnet v0.2 2026-07-06 21:46:31 +09:00
Jinho Jang 96be50d8d0 refactor: make @repo/content the single source for home, get-started & movement page copy (#62)
* docs: add page-copy single-source spec and implementation plan

* feat(content): add typed home section schemas and migrate home copy into content

Add 7 bespoke home* section schemas (+ techStack ctas) to the page section
union and populate content/pages/en/home.json with home copy moved verbatim
from messages.

* refactor(web): source home page copy from @repo/content; drop home namespace from messages

Feed every home section from content via findSection/data props (replacing
next-intl getTranslations), remove the home namespace from messages/en.json,
and add a regression guard plus content-based tests. Render output unchanged.

* docs: add get-started/movement page-copy dedup spec and plan

* feat(content): add get-started & movement section schemas and content docs

Move get-started/movement page copy verbatim into content/pages so it is
single-sourced in @repo/content.

* refactor(web): source get-started/movement copy from content; drop those namespaces from messages

Feed both pages from content via findSection/data props (replacing next-intl),
switch them to content-based metadata, delete pages.getStarted/movement from
messages, and add content-based tests + ownership guard. Render unchanged.

* docs: add navbar-pages CMS copy spec and plan

* feat(content): add navbar page section schemas + Pages docs (book, brand-kit, research, node-programme, lambda-prize, manifesto, media, podcast, broadcast, tech-stack explorer)

* refactor(web): source navbar pages copy from content; drop those namespaces from messages

Migrate book, brand-kit, research (incl. rich-text via a tagged-text renderer),
node-programme, lambda-prize, manifesto, media, podcast, logos-broadcast-network,
and the tech-stack explorer to read copy from content via findSection/data props
and content-based metadata; remove the migrated pages.* namespaces from messages;
update guards + contracts. Render output unchanged.

* feat(web): source activist/coalition pages copy from content (CMS title/description)

Migrate activistBuilder, activistLeaderSteward, and coalitionPartner page
metadata from next-intl messages.pages.* into @repo/content PageCopy JSON,
following the established bookCopy pattern.

- Add activistBuilderCopySectionSchema, activistLeaderStewardCopySectionSchema,
  coalitionPartnerCopySectionSchema to packages/content/src/schemas/pages.ts
  and wire them into the pageSectionSchema discriminated union
- Create content/pages/en/activist-builder.json,
  activist-leader-steward.json, coalition-partner.json with verbatim
  title/description/heading values from the old messages namespace
- Switch all three route pages from createTranslatedPageMetadata to
  createPageMetadata(ROUTE), removing the NAMESPACE constant
- Remove activistBuilder, activistLeaderSteward, coalitionPartner from
  apps/web/messages/en.json pages object
- Add schema parse tests, renderToStaticMarkup page tests, messages ownership
  guards, and content-route contract entries for all three pages

* chore(web): remove dead faq (pages.faq + unused FaqSection components)

* fix: remove stale faq links

* fix: pass payload env through turbo build

* fix: preserve lambda prize support labels

* fix: preserve live copy after master rebase

* fix: address page copy review feedback

* refactor: move design guide copy to content

* feat: add schemas for home and page copy sections

- Introduced home section schemas including homeSocialProof, homeChoosePath, homeDecide, homeStartBuilding, homeAbout, homeUseCases, and homeBuilderPortal.
- Added page copy section schemas such as getStartedCopy, movementCopy, bookCopy, designGuideCopy, activistBuilderCopy, activistLeaderStewardCopy, coalitionPartnerCopy, researchCopy, nodeProgrammeCopy, lambdaPrizeCopy, mediaCopy, podcastCopy, broadcastCopy, and manifestoCopy.
- Created shared schema for section keys to maintain consistency across sections.

* chore(web): remove dead pages.faq from messages

* fix: resolve page copy migration conflicts

* fix: source connect form page copy from content

* fix: remove duplicated afform page copy exports

* fix: stop generating afform page copy exports

* fix(cms): require only server actions key

* fix(cms): load env files before build validation

* fix(cms): make env loader available during build

* fix(cms): load next env via require

* test(cms): isolate missing env validation

* feat: add homepage testnet highlight

* fix: restore lambda prize support links
2026-07-06 19:09:02 +09:00
jinhojang6 938fde7747 fix: make testnet faq route versionless 2026-07-03 12:27:07 +09:00
jinhojang6 2be0b14585 test: keep field guide link policy copy-neutral 2026-07-01 16:26:18 +09:00
jinhojang6 8f430fc200 fix: remove duplicated field guide wording 2026-07-01 16:26:18 +09:00
jinhojang6 40c7607466 Revert "fix: render field guide protocol template as prose"
This reverts commit 9f4d738dc0.
2026-07-01 16:26:18 +09:00
jinhojang6 437f3f0ee8 fix: render field guide protocol template as prose 2026-07-01 16:26:18 +09:00
jinhojang6 d087a9c5ef fix: repair field guide chapter links 2026-07-01 16:26:18 +09:00
jinhojang6 0e1e7f9cb0 fix(web): restore favicon visibility 2026-07-01 01:18:57 +09:00
jinhojang6 8cefcfffcc fix(design-guide): update asset download copy 2026-06-30 02:29:00 +09:00
jinhojang6 25f4d17ba2 feat(design-guide): apply Design Guide labels, URL refs, and i18n (#66)
Completes the /brand-kit -> /design-guide move: page namespace/heading,
ROUTES.designGuide references (sitemap, docs ToC, design-systems demo),
nav/footer labels, perf budget route, and i18n keys.
2026-06-30 02:29:00 +09:00
Jules 67585e09a3 feat: limit funnel form fields to 2000 chars
* feat: clamp civi-crm notion text properties to 2000 chars

Extract the rich_text slice into a named NOTION_TEXT_MAX_LENGTH
constant and also clamp the Name title, which was previously unbounded.

* feat: limit web funnel form fields to 2000 chars

Add a 2000-char zod max and maxLength cap on text/textarea fields, and
show a live x/2000 character counter under each textarea.

* feat: flag over-limit form fields instead of trimming

Remove the hard maxLength cap so users can type/paste past the 2000-char
limit; turn the field red with an inline counter and message once over,
keeping submission blocked by the existing schema.

* feat: scroll to first invalid field on blocked submit

* refactor(civi-crm): move NOTION_TEXT_MAX_LENGTH to notion constants
2026-06-26 20:04:33 +09:00
jinhojang6 8f95bba72b fix: refine technology stack use case cards 2026-06-20 11:26:41 +09:00
jinhojang6 e17f6626bb feat(web): implement technology stack use cases logic and tests 2026-06-20 11:00:57 +09:00
jinhojang6 2078be3240 feat(web): integrate winnable issues count into social proof section 2026-06-20 00:08:31 +09:00
jinhojang6 64b1761d1c fix(web): update event data and correct spelling in technology stack description 2026-06-19 18:35:05 +09:00
jinhojang6 df025284b2 feat(web): add circles map attribution component and integrate into circles map section; update settings and tests 2026-06-19 17:34:59 +09:00
jinhojang6 4a70bc3f5a fix: update responsive styles and improve layout across various components 2026-06-19 00:31:34 +09:00
jinhojang6 d40ee16ce1 chore: remove monorepo test coverage implementation plan document 2026-06-18 01:49:38 +09:00
jinhojang6 333c95c250 fix: update documentation links to point to the new site 2026-06-18 01:05:59 +09:00
jinhojang6 d094c0c2c8 fix: link manifesto related reading 2026-06-17 23:24:48 +09:00
jinhojang6 0eb2a5a907 fix: update Lambda Prize text for improved clarity and structure in UseCasesSection 2026-06-17 22:24:28 +09:00
jinhojang6 d943891fcd fix: link get started install cards 2026-06-17 19:57:57 +09:00
jinhojang6 fd753b765e feat: enhance homepage sections with new data structure and components; refactor social proof stats handling and tests 2026-06-16 11:56:58 +09:00
jinhojang6 d1172c6b1c refactor: address Umami tracking review comments 2026-06-13 22:11:44 +09:00
jinhojang6 562bdfb608 feat: track sitewide Umami clicks 2026-06-13 22:11:44 +09:00