Agent Skills: Questions & Answers System

Expert assistant for the Questions & Answers (Q&A) system in Raamattu Nyt. Develop, debug, and extend the full pipeline from search query classification through question upsert, AI answer generation, community answers, voting, admin moderation, and anchor questions. Use when (1) adding features to Q&A search results, (2) modifying question classification logic, (3) working on community answer forms or moderation, (4) changing AI answer generation or prompts, (5) editing admin Q&A management pages, (6) working with anchor questions or similar questions, (7) modifying votes/upvotes/downvotes, (8) fixing Q&A-related bugs, (9) extending answer types or display, (10) working on QuestionPage, QuestionsSearchSection, or AdminQuestionsPage.

UncategorizedID: Spectaculous-Code/raamattu-nyt/questions-answers

Install this agent skill to your local

pnpm dlx add-skill https://github.com/Spectaculous-Code/raamattu-nyt/tree/HEAD/.claude/skills/questions-answers

Skill Files

Browse the full folder contents for questions-answers.

Download Skill

Loading file tree…

.claude/skills/questions-answers/SKILL.md

Skill Metadata

Name
questions-answers
Description
|

Questions & Answers System

Full-stack Q&A pipeline: search → classify → upsert → answer (AI/community/admin) → moderate → publish.

Architecture Overview

User search query
  → questionClassifier.ts (client-side)
  → upsert-question (edge fn: normalize, hash, classify, deduplicate)
  → QuestionsSearchSection renders:
      ├── Answers grouped by type (bullet → editorial → community → AI)
      ├── Info-palaset (info blocks attached to the question — see below)
      ├── CommunityAnswerForm (auth-gated)
      ├── QuestionConsentBlock (AI generation / human request)
      ├── AnchorQuestionsSection (topic-linked curated Q&A)
      └── SimilarQuestionsSection (trigram similarity)

Key Files

Edge Functions

| Function | Path | Purpose | |----------|------|---------| | upsert-question | supabase/functions/upsert-question/ | Normalize, classify, upsert, find similar | | generate-ai-answer | supabase/functions/generate-ai-answer/ | AI answer via Claude (paid, quota-checked) | | submit-community-answer | supabase/functions/submit-community-answer/ | User-submitted answer (free, needs admin approval); bot-gated | | submit-feedback | supabase/functions/submit-feedback/ | Anonymous app feedback (bot-gated; replaces the old direct anon insert) | | vote | supabase/functions/vote/ | +1/-1 voting on questions/answers |

All three anon-callable submit functions (upsert-question, submit-community-answer, submit-feedback) run _shared/verify-human.ts (honeypot + Turnstile + throttle) first.

Hooks (apps/raamattu-nyt/src/hooks/)

| Hook | File | Purpose | |------|------|---------| | useUpsertQuestion | useQuestions.ts | Mutation: upsert question via edge fn | | useGenerateAiAnswer | useQuestions.ts | Mutation: generate AI answer (quota-checked) | | useSubmitCommunityAnswer | useQuestions.ts | Mutation: submit community answer | | useVote | useQuestions.ts | Mutation: vote on question/answer | | usePublishedQuestion | useQuestions.ts | Query: single published question by slug | | useQuestionAnswers | useQuestions.ts | Query: published answers for question | | useAdminQuestions | useQuestions.ts | Query: all questions with answer stats | | useAdminAnswers | useQuestions.ts | Query: all answers (including unpublished) | | useAnchorQuestions | useAnchorQuestions.ts | Query: anchor Q&A grouped by topic |

Components (apps/raamattu-nyt/src/components/search/)

| Component | Purpose | |-----------|---------| | QuestionsSearchSection | Main Q&A display in search results | | QuestionConsentBlock | AI answer / human request consent | | CommunityAnswerForm | User answer submission form | | SimilarQuestionsSection | Trigram-similar questions | | AnchorQuestionsSection | Topic-linked curated Q&A |

Pages

| Page | Route | Purpose | |------|-------|---------| | QuestionPage.tsx | /kysymys/:slug | Public single question view + admin tools | | AdminQuestionsPage.tsx | /ohjaamo/questions | Full admin Q&A dashboard | | PremiumQuestionsPage.tsx | /premium/questions | Public questions browsing | | AdminGuestAuthorsPage.tsx | /ohjaamo/guest-authors | CRUD for Vieraskynä (guest) authors |

Core Libraries

| File | Purpose | |------|---------| | src/lib/questionClassifier.ts | Client-side question detection (Finnish) |

Database Schema (bible_schema)

For full schema details: See references/schema.md

Tables

  • questions — Normalized questions with classification, status, voting
  • answers — Answers with author_type, rich content, key_verses, topics, strongs_refs
  • votes — User votes (+1/-1) on questions/answers (unique per user)
  • topic_anchor_questions — Links questions as anchor Q&A for topics

Answer Author Types

| Type | Created by | Published by default? | |------|-----------|----------------------| | bullet | Admin | Yes | | admin | Admin | Yes | | system | Import/seed | Yes | | ai | Edge function | No (needs admin review) | | community | Authenticated user | No (needs admin approval) | | guest | Admin (Vieraskynä) | Yes — attributed to a guest_authors row via guest_author_id |

Author display: account authors (author_id) resolve their public name via the get_author_public_info RPC (profiles RLS is admin-or-self); guest authors (guest_author_id) show display_name/@username from guest_authors. See Vieraskynä.

Question Status Flow

draft → needs_review → approved → published
                    └→ rejected

RLS Summary

  • Anon/Auth: Read published questions + published non-hidden answers
  • Auth: Read own questions + own community answers, insert votes, submit community answers
  • Admin: Full CRUD on all questions, answers, votes

Common Patterns

Add new answer type

  1. Add CHECK constraint value in migration
  2. Update AnswerData interface in QuestionsSearchSection.tsx
  3. Update Answer type in useQuestions.ts
  4. Add rendering logic in renderAnswerCard
  5. Update admin management in AdminQuestionsPage.tsx

Add new question classification

  1. Update classifier patterns in questionClassifier.ts
  2. Mirror logic in upsert-question edge function
  3. Update CHECK constraint if new DB value needed
  4. Add UI handling in QuestionsSearchSection

Modify AI answer generation

  1. Edit prompt template via ai-prompt-manager skill
  2. Edge function: generate-ai-answer/index.ts
  3. Output validation: excerpt, key_verses, topics, strongs_refs, related_questions
  4. Model/vendor configured via ai_feature_bindings table

Add feature to community answers

  1. Edge function: submit-community-answer/index.ts
  2. Hook: useSubmitCommunityAnswer in useQuestions.ts
  3. Form: CommunityAnswerForm.tsx (search) or inline in QuestionPage.tsx
  4. Admin tab: "Yhteisövastaukset" in AdminQuestionsPage.tsx

Vieraskynä (guest authors)

Account-less named attribution for answers AND info-palat. An admin can credit a piece to a bible_schema.guest_authors row (a named contributor with no login) via guest_author_id + author_type='guest', instead of a real account.

| Concern | File | |---------|------| | List/CRUD hook | hooks/useGuestAuthors.ts (RPC get_guest_authors for active list; bibleSchemaTable("guest_authors") for admin CRUD) | | Admin picker | components/admin/GuestAuthorSelect.tsx (used in NewAdminAnswerForm, InfoBlockEditor) | | Admin page | pages/AdminGuestAuthorsPage.tsx (/ohjaamo/guest-authors) | | Public name resolve | hooks/useUsernamesByIds.ts → RPC get_author_public_info(uuid[]) | | Render | question-detail/AnswerCard.tsx, question-detail/InfoTextCard.tsx |

Gotcha: account authors' public names come from get_author_public_info (SECURITY DEFINER) because public.profiles SELECT is admin-or-self — a direct profiles read returns nothing to guests, so named authors would silently not render. The old account-only AuthorSelect.tsx + lib/designatedAuthors.ts were removed — don't reference them.

Anonymous-submission bot protection

Anon submit paths (upsert-question, submit-community-answer, submit-feedback) gate on supabase/functions/_shared/verify-human.ts: honeypot (empty hidden field + ≥1500 ms on screen), Cloudflare Turnstile (verified via TURNSTILE_SECRET_KEY; skipped when the secret is unset, so dev/staging just work), and a rate-limit (check_and_record_throttlepublic.submission_throttle, service_role-only). Client widget/fields: components/security/HumanCheckFields.tsx + useHumanCheck.ts (wired into QuestionConsentBlock, AskQuestionButton, AppFeedbackForm). When adding a new anon submit path, add the fields client-side and call verifyHuman(...) server-side.

AI answers hidden from users

AI answer generation still exists (ai answer type, generate-ai-answer) but its UI is hidden from non-admin userslib/aiVisibility.ts#shouldHideAIForUser(plan) returns true for everyone except admin (AI quotas are zeroed for all user plans). Gate any AI button/panel/token display behind it. Exception: plan-from-summary (kooste→lukusuunnitelma) is NOT gated — it stays visible for Pro/Premium.

Info-palaset (info blocks)

Reusable content blocks (bible_schema.info_blocks) attached to questions, topics, and spiritual paths. One of two answer mechanisms for a question (alongside community answers). Admin-curated — no AI generation and no classifier (unlike questions).

[!important] Key invariant An info-pala is rendered by TWO components: InfoTextCard (page-tila) and InfoView (cinema-tila). Edit whichever the request targets and keep both in sync.

Tables (bible_schema)

  • info_blocksslug, title_fi/en, body_md/en, body_html/en, excerpt/en, is_published, is_handled, is_hidden, is_core (gates attachability), author_type (admin/community/system), icon, bg_color, image_url, key_verses (display-only), key_prayers, action_links (route/url/grand_plan; legacy action_grand_plan_id).
  • question_info_blocks / topic_info_blocks / spiritual_path_info_blocks — junctions (sort_order; paths also carry binding_id).

RPCs

  • get_info_blocks_by_slugs(p_slugs) — public, published blocks by slug
  • get_attachable_info_blocks() — admin-only (SECURITY DEFINER), core blocks + attach counts
  • get_spiritual_path_info_block_slugs[_admin]() — path bindings

Key files

| Concern | File | |---------|------| | CRUD hook | hooks/useInfoBlocks.ts | | Fetch by slug | hooks/info-blocks/useInfoBlocksBySlugs.ts | | Question-attached | hooks/questions/useQuestionInfoBlocks.ts (+ admin variant) | | Topic-attached | hooks/topics/useTopicInfoBlocks.ts | | Path bindings | hooks/info-blocks/useSpiritualPathInfoBlock{Bindings,Mutations}.ts | | Page render (1 block) | pages/question-detail/InfoTextCard.tsx (wrapper QuestionInfoSection.tsx) | | Cinema render | components/cinema/InfoView.tsx (1) · InfoFlow.tsx (snap-scroll N) · InfoCinema.tsx (shell) | | Attach dialog | pages/question-detail/AddInfoBlockDialog.tsx · components/info-blocks/InfoBlockAttachDialog.tsx | | Action links | components/info-blocks/InfoBlockActions.tsx | | Path render | components/spiritual-path/SpiritualPathInfoBlock.tsx | | Admin | pages/AdminInfoBlocksPage.tsx (/ohjaamo/info-blocks) · pages/admin-info-blocks/InfoBlockEditor.tsx |

Q&A cinema integration

QuestionCinemaApp renders published community answers (via answerToInfoBlock()) + attached info blocks through the same InfoFlow/InfoView — answers first, then info blocks.

Common patterns

  1. Create/edit an info-palaInfoBlockEditor (fill both FI + EN; is_core gates attachability).
  2. Attach to question/topic/path → attach dialog → junction row with sort_order.
  3. Change appearance → page = InfoTextCard, cinema = InfoViewedit both.
  4. Info blocks are read via RPC + typed bibleSchemaRpc (not direct .from("info_blocks")) → mock those in tests.

Full info-block schema + RLS: See references/schema.md.

Shared UI Components

  • BibleReferencePickerPopover — Verse picker popover (used across 6 files)
  • SafeTextarea — Input with XSS protection, markup detection
  • RichContentRenderer — Renders body_html from rich editor
  • VerseContentCard — Displays verse reference with content
  • FeedbackThumbs / FeedbackText — Answer feedback (AI answers)

References