Skip to content

Feat/help center - #30

Merged
guoliim merged 67 commits into
linkcraftstudio:mainfrom
guoliim:feat/help-center
Sep 8, 2026
Merged

Feat/help center#30
guoliim merged 67 commits into
linkcraftstudio:mainfrom
guoliim:feat/help-center

Conversation

@guoliim

@guoliim guoliim commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Adds a Help Center module: an admin surface for writing and organizing articles, a public portal for readers, and full-text search across
both languages.

  • Admin (/dashboard/help) — collections and articles in two views (collections with inline articles, or a flat article list),
    drag-to-reorder, checkbox multi-select with bulk publish/unpublish, and title search that forces the flat view.
  • Editor — title, description and Markdown body using the existing editor; draft/published states with no autosave (explicit Save
    draft
    / Publish); a properties panel with the collection picker, timestamps and the article URL.
  • Portal — help center home with collection cards, a collection page, and an article page laid out as a doc: breadcrumb, body, and a
    sticky two-level On this page table of contents with copyable heading anchors.
  • Search — Postgres full-text over title (weight A), description (B) and body (C). Chinese is indexed as CJK bigrams alongside the
    English dictionary, so both languages are searchable; title hits rank above body-only hits.
  • URLs and SEO/help/{shortId}-{slug}, slug derived from the title (pinyin for Chinese) and refreshed when the title changes,
    with old slugs 301-ing to the current one. Canonical, Article + BreadcrumbList JSON-LD, hreflang, and sitemap entries for published
    articles only. Archived articles return 410, drafts and articles under a hidden collection return 404; both are noindex.
  • Visibility — collections carry a visible flag rather than a draft state; hiding a collection hides its articles from readers
    without touching their published status, and the editor warns when that is the case.

67 commits, 51 files, +5720/-39. No new environment variables.

Why

FeedLog collects what users want but has nowhere to answer them. Teams running a public board end up repeating the same explanations in
comments, or sending people to a separate documentation tool that does not share the product's branding, domain or language settings. A
Help Center in the same portal lets a team answer once, link the answer from feedback threads, and keep readers on their own domain.

Closes #

How to review

Suggested reading order:

  1. server/db/schemas/help.ts and server/db/migrations/0012_strange_mindworm.sql — two tables, help_collection and help_article.
  2. server/utils/help.ts — short id, slug generation (including the pinyin path) and the tsvector builder.
  3. server/api/admin/help/** then server/api/help/** — the admin and public halves of the API.
  4. app/pages/dashboard/help/** and app/components/help/** — the admin UI.
  5. app/pages/help/** — the portal.

Two things that look odd on purpose:

  • help_article.collection_id has no foreign key. The cascade lives in server/api/admin/help/collections/[id].delete.ts, which
    deletes the collection and its articles in one transaction, ordered so that an unauthorized delete 404s and rolls back before any article
    is touched. This is deliberate, not an oversight.
  • The collections view has no pagination while the flat article view paginates at 10. The two views deliberately differ; the footer
    only renders for the paginated one.

To try it locally: create a collection from New → New collection, add an article, publish it, then open /help on the same host.
Search for a word that appears only in the body to exercise the ranking, and try a Chinese title to exercise the pinyin slug and bigram
index.

Checklist

  • Commits are signed off (git commit -s)
  • Tests pass locally (pnpm build)
  • If schema changed, migration was generated (pnpm generate:migration) and committed
  • If public API or env vars changed, README.md / .env.example / docs/deploy/* updated
  • No secrets, internal URLs, or team member names in the diff

Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
…the dragged collection

Signed-off-by: guoliim <guoliim@users.noreply.github.com>
… stale search

Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
…ctly

Signed-off-by: guoliim <guoliim@users.noreply.github.com>
…he page scroller

Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
…e shells

Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
Signed-off-by: guoliim <guoliim@users.noreply.github.com>
…e computeds

Signed-off-by: guoliim <guoliim@users.noreply.github.com>
…s to it

Signed-off-by: guoliim <guoliim@users.noreply.github.com>
…e in app code

Signed-off-by: guoliim <guoliim@users.noreply.github.com>
@guoliim
guoliim merged commit 15156fb into linkcraftstudio:main Sep 8, 2026
2 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