[GPT-163] Suggested articles improvements#484
Merged
Hackin7 merged 14 commits intoJun 8, 2026
Conversation
Replaces the bottom-of-article widget's "any shared tag" matching with Typesense semantic ranking on title+excerpt, with the existing tag-matched SSR rendering serving as both the no-JS fallback AND the topup pool when Typesense returns < 3 hits. Cross-collection scope. 3-card layout preserved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8 tasks: Typesense plumbing (config + wrapper), post-card data-slug, Alpine component with card builder, partial wrapper changes, main.js registration, README, end-to-end manual verification. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three cleanups from code review of Task 4:
1. The defensive filter dropping the source post compared h.slug to
source.id — different value spaces (slug vs ObjectId hex), so the
guard was dead code. Add 'id' to Typesense include_fields and
compare on h.id, restoring the safety net the comment claimed.
2. Document the two intentional divergences from post-card.hbs in the
buildCardElement comment block: hardcoded "feed-card post" instead
of {{post_class}} (verified safe — no CSS targets per-post tag-{slug}
classes), and omitted <img> when feature_image is empty (post-card
uses a hidden placeholder; both are display:none).
3. pickPrimaryTag drops the type-confusing "|| hit.tags" fallback —
tags.name is always requested in include_fields so the fallback
couldn't help, and hit.tags has a different shape that would make
names[i] return an object instead of a string.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a new section explaining the three custom-config fields, the search-only key security model, and where to override per-install. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final-review item: makes the JS↔HBS mirror contract discoverable from the HBS side. A future contributor changing post-card.hbs now has a breadcrumb to assets/js/related-posts.js → buildCardElement(). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…empty
User reported the recommended-articles widget was including the article
itself. Live verification of Typesense's filter_by=id:!=X showed it
works correctly, so the bug must be on our side: source.id from
data-post-id="{{post.id}}" was likely empty for some reason, making
the wrapper's `if (excludeId)` gate skip the filter, AND making the
JS defensive filter useless (h.id !== '' is always true).
Three layers of defense added:
1. Partial now exposes data-post-slug="{{post.slug}}" alongside
data-post-id. Slug is the most reliable identifier (also in the
page URL and on every post-card).
2. typesense-search.js gains an excludeSlug option. When both are
passed, filter_by combines them with && so a doc matching either
identifier is excluded server-side.
3. JS defensive filter now checks id, slug, AND url — any one match
triggers exclusion. Empty source identifiers are skipped (no false
positives where empty string matches empty field).
Verified live: filter_by=slug:!=X excludes the named post correctly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-site jumps) Typesense indexes posts with production-absolute URLs (https://advisory.sg/...). The JS card builder consumed hit.url verbatim for both the inner div onclick and the stretch-link href, so clicking a related card on localhost:2368 navigated the user OFF-SITE to the prod domain. SSR cards were already correct because Ghost's {{url}} helper resolves against @site.url. Add a toLocalPath() helper that returns just pathname+search+hash from any URL (absolute, relative, or malformed). The browser then resolves the result against the current page's origin: localhost stays on localhost, prod stays on prod, no environment-specific config. Apply at three sites: the onclick interpolation, the stretch-link href, and the defensive self-exclusion URL check (which previously compared full URLs and never matched on localhost — now compares paths). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
wei2912
self-requested a review
May 19, 2026 14:31
Member
|
Notes to self to focus on:
|
…improvements # Conflicts: # README.md # assets/js/main.js # assets/js/typesense-search.js # default.hbs # partials/post-card.hbs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces the bottom-of-article widget's "any shared tag" matching
with Typesense semantic ranking on title+excerpt, with the existing
tag-matched SSR rendering serving as both the no-JS fallback AND the
topup pool when Typesense returns < 3 hits. Cross-collection scope.
3-card layout preserved.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com