diff --git a/www/package.json b/www/package.json index e8825cd..47ec5b4 100644 --- a/www/package.json +++ b/www/package.json @@ -21,6 +21,7 @@ "test:markdown": "node --test tools/markdown.test.mjs", "test:publication": "node --experimental-strip-types --test tools/publication.test.mjs", "test:seo": "node --experimental-strip-types --test tools/seo.test.mjs", + "test:library": "node --experimental-strip-types --test tools/library.test.mjs", "generate:sitemap": "node tools/generate-sitemap.mjs", "generate:redirects": "node tools/generate-redirects.mjs" }, diff --git a/www/reports/migration-summary.md b/www/reports/migration-summary.md index 79fb324..ed74662 100644 --- a/www/reports/migration-summary.md +++ b/www/reports/migration-summary.md @@ -2131,3 +2131,301 @@ recomputed from the reconciled records. No functional source file, dependency, route, redirect rule, content record, Nix/nginx/deployment configuration, publication policy, or generated application behavior was changed in Phase 6A. + +## Phase 6A Architecture Ratification — October 9, 2026 + +The Phase 6A proposal was ratified against checkpoint +`76a035424dc5245443476b168d8c91ea62eed27b` with the following decisions. The +original proposal remains preserved as historical architecture research; +these later decisions govern Phase 6B implementation. + +1. **Search architecture approved.** Library search uses the existing content + collections and publication manifest, locale-specific static JSON indexes, + and dependency-free project-owned TypeScript. Initial fields are title, + description, headings, category, author display information, publication + date, and future nonempty tags. Full-body indexing, fuzzy matching, + stemming, hosted services, query logging, and third-party dependencies remain + deferred or disallowed. +2. **Library pagination approved for a later checkpoint.** The agreed model is + ten posts per page, publication date descending, normalized route ascending + as a stable tie-breaker, the unnumbered root as page 1, nonempty generated + pages 2+, and HTTP 301 redirects from `/page/1` aliases. Phase 6B.1 does not + implement this decision. +3. **Legacy `/search` approved with a narrower initial scope.** It preserves + `term`, `q`, repeated `type`, and `offset` compatibility but searches only + Library articles. It is `noindex`, sitemap-excluded, and outside the + hreflang graph. This intentional Library-only behavior differs from + HubSpot's broader production site search. +4. **Archive indexing policy modified.** Future numbered Library pages are + self-canonical, indexable, and sitemap-eligible. Future author roots and + numbered author pages are initially `noindex,follow` and omitted from the + sitemap. This replaces the proposal's recommendation to index all archive + pages immediately. Phase 6B.1 creates no author routes. +5. **Syndicated-article handling approved.** The two accessible English records + `community-highlight-meet-rula-diab` and `new-aia-scottsdale-office` remain + available in Library listings and internal search while retaining their + external canonicals, `noindex`, sitemap exclusion, and hreflang exclusion. +6. **Author-root hreflang conditionally approved for later work.** It requires + explicit validated author translation keys plus eligible, indexable author + roots. Numbered author pages never receive hreflang merely from matching page + numbers. Because author archives initially remain `noindex`, Phase 6B.1 adds + no author hreflang. +7. **Author content model approved for later work.** An optional localized + `displayName` may reproduce source-supported archive headings, while the + person's actual name remains the BlogPosting Person attribution. Biography + reconciliation and substantive Spanish/Arabic edits require source evidence + and human language review. Phase 6B.1 uses the existing author schema. +8. **Phase 6C sequencing approved with a cutover constraint.** Pagination may + precede reconciliation of the nine missing English articles, but no records + or empty pages may be fabricated. Production `/library/page/6` and + `/library/author/rula-diab/page/6` cannot be accepted as migrated until + Phase 6C supplies the necessary source-supported content. + +The ratification therefore modifies four material recommendations in the +original proposal: `/search` initially searches Library content rather than the +whole site; author archives begin as `noindex` and outside the sitemap; +author-root hreflang waits for later content and indexing approval; and Library +pagination may be implemented before Phase 6C even though production page-6 +acceptance may not. Only the search-related parts of Decisions 1, 3, and 5 were +authorized for Phase 6B.1. Pagination, author archives, redirects, and Phase 6C +content retain separate review gates. + +## Phase 6B.1 — Shared Library Catalog and Search — October 9, 2026 + +**Starting checkpoint:** `76a035424dc5245443476b168d8c91ea62eed27b` + +### Scope and Architecture + +Phase 6B.1 implements one shared Library catalog, three generated static search +indexes, progressively enhanced locale-scoped search on the three Library +roots, and a static `/search` compatibility page. It adds no pagination or +author archive routes, changes no article body or date, installs no dependency, +and sends no query to an external service. + +`src/utils/library-catalog.ts` is the single reusable catalog layer. It consumes +generated blog entries from the Phase 3A publication manifest, resolves authors +from the existing localized author records, and returns immutable metadata +records separated by locale. It includes route-eligible accessible records even +when they are `noindex` or externally canonicalized, while excluding drafts and +other non-generated records. Results sort by publication date descending and +normalized local route ascending; duplicate routes, missing authors, and +ambiguous authors fail explicitly. That ordering and data shape can be reused by +the later approved pagination work without maintaining a second article list. + +The catalog extracts bounded Markdown/MDX headings while ignoring fenced code, +component syntax, and non-heading body content. A record contains only: + +- stable local URL and locale; +- title and description; +- at most 50 meaningful headings, each capped at 300 characters; +- category and future nonempty tags; +- localized author name and slug; and +- publication date. + +It does not serialize article bodies, rendered HTML, canonicals, form data, +visitor data, analytics identifiers, credentials, cookies, draft metadata, or +runtime secrets. `src/utils/library.ts` adapts the pure catalog to Astro content +collections and memoizes it for a build. The JSON endpoint at +`src/pages/assets/search/library/[locale].json.ts` creates deterministic UTF-8 +assets for `en`, `es`, and `ar` on every build. + +### Search Normalization and Ranking + +`src/utils/library-search.ts` owns DOM-independent matching and compatibility +query parsing. It applies Unicode NFKC normalization, locale-aware lowercasing, +collapsed whitespace, punctuation normalization, Spanish accent-insensitive +matching, and removal of Arabic combining marks and tatweel while preserving +original display text. It does not stem or fuzzily rewrite words. Documented +partial-token matching begins at three characters to accommodate useful prefix +and attached-particle matches without turning short tokens into broad matches. + +All normalized query terms must match. Deterministic weights favor an exact +title phrase, then title tokens, headings, description, category/tags, and +author metadata. Equal scores sort by publication date descending and route +ascending. Empty or whitespace-only input restores ordinary browsing. Unusual +punctuation and Unicode input, no matches, one match, multiple matches, and +cleared queries are covered by the focused test suite. + +### Embedded Library Search + +`LibrarySearch.astro` supplies the shared accessible interface for `/library`, +`/es/library`, and `/ar/library`. Each root loads only its own locale index on +the first real search. The existing server-rendered list remains complete (46 +English, 12 Spanish, seven Arabic) because ten-item pagination is deferred. +Search state uses a `#search=` fragment, supports direct links and browser +back/forward navigation, and avoids sending embedded queries to a static server. +Clearing a query restores the server-rendered list and its pre-search fragment. + +Controls become visible only after successful client initialization. The +ordinary listing and links remain available without JavaScript; a `