SPEC_ANSWERHUB_SHELL.md

AnswerHub — foundations and page frame — spec

What this covers: making AnswerHub read as a finished web page rather than a working scaffold — Chinese typography, a shared page shell, real form labels, a theme control, consistent product vocabulary — plus the family-wide parts of that which apply to SiaoHub as well.

For what is actually built, deployed or reachable, see ClaudeDocs/HANDOFF.md — status deliberately does not live here.

This spec does not change the family's visual direction. Colors, tone, the sage accent and the Notion/Linear restraint all stay exactly as ClaudeDesign/APP_FAMILY.md records them. It changes the things underneath that: what font renders Chinese, what frame a page sits in, whether a form field has a label.

It sits alongside SPEC_ANSWERHUB_LIVE.md and reorders part of it: the browser walk in that spec now happens after this work, against the rebuilt interface, so the owner is asked for their time once rather than twice. It supersedes nothing.

No issue tracker exists for this project, so this spec is filed as a ClaudeDocs/SPEC_* document — the same precedent SPEC_APPS.md recorded.

Problem Statement

The owner looked at AnswerHub in a browser for the first time and said it plainly: this is not what a normal modern web page looks like.

That judgement is correct, and it is not a matter of taste. Ten specific things are wrong, all of them measurable rather than aesthetic:

Nothing anywhere specifies a Chinese font. The product is written entirely in Traditional Chinese, and the font stack names Geist — loaded with the Latin subset only — followed by the generic system sans. None of those contain a single Han glyph. Every Chinese character on the site is being rendered by whatever the browser falls back to, which differs per operating system and was never chosen by anyone. The Chinese text — which is all the text — is the one part of the page nobody has designed.

The page has no frame. Nine files each hand-write the same magic number to subtract a header's height from the viewport. Five different maximum widths are in use across the product. Four pages — settings, profile, upload and bulk upload — center a small card vertically in an otherwise empty screen, which is what a scaffold looks like, not a page. There is no footer anywhere, so content simply stops. And there is no link from anywhere in AnswerHub back to apps.siao.ai or siao.ai: a visitor who arrives is stranded.

Forms have no labels. Every field is identified by its placeholder alone, so the label vanishes the moment the user types, and a screen reader has nothing to announce. One raw, unstyled file input survives — the last of the bare HTML form elements the restyle set out to remove.

Smaller things that add up. The document declares lang="en" for an entirely Chinese product. Every page in the product shares one <title>, so every browser tab reads "apps.siao.ai". Emoji stand in for icons while lucide-react sits installed and completely unused, which means the icons render differently on every platform. There are no route-level loading, error or not-found states at all, which is the direct cause of "I clicked and nothing happened" and "it went blank". Dark mode follows the operating system only, with no control — and the CSS carries a .dark class that nothing in the application ever applies, so it is dead code. A CSS variable is defined in terms of itself and therefore resolves to nothing.

The product cannot decide what it calls things. Pages written before the rename say 作業 (homework); pages written after say 解答 (answer); the homepage manages both 解答 and 答案 in a single sentence. The product is named 答案庫.

The same Chinese-font gap exists in SiaoHub, for a different reason. Its own interface is entirely English, so its chrome is unaffected — but everything it renders comes from Forgejo: repository descriptions, README bodies, file contents, much of it Chinese. Its font stack names only Latin faces too. SiaoHub also has no footer element.

Solution

Fix the foundations and rebuild the page frame, without touching the visual direction.

Chinese gets a font stack that names actual Chinese fonts — the platform faces, in a deliberate order, in both applications. No download, no external request, and no possibility of a missing glyph. The alternative of self-hosting a subsetted face is specifically rejected, and this project has already reasoned its way there once: SiaoHub's preview-card renderer records that subsetting CJK trades a guaranteed correct render for an occasional broken one, in exactly the cases — a rare surname, a technical term — most likely to matter. AnswerHub renders text its users type, so it can never know the character set in advance. The marketing homepage can subset because its copy is fixed and a test catches missing glyphs when the copy changes; AnswerHub has neither property.

One shared page shell replaces nine improvisations. Header, a content region with a settled width, and a footer — decided once, used by every page. Pages align from the top and carry a real page heading. The four floating-card pages become pages. The footer carries the way back to apps.siao.ai and siao.ai, which is also the fix for AnswerHub being an island.

Every form field gets a visible label above it, using a shadcn Label — the first genuinely-needed new primitive since the restyle, which is the bar APP_FAMILY.md sets for adding one. The last bare file input gets wrapped.

Dark mode gets a control, adopting SiaoHub's existing mechanism rather than inventing a second one, so the two applications behave identically and the dead class becomes live.

The product settles on 解答, matching its own name, and retires 作業 as the Lwopan-era leftover it is.

Everything is verified, not eyeballed: accessibility checks across every page, and visual snapshots of the pages that carry the most layout decisions, in both themes.

User Stories

Reading Chinese

  1. As a Traditional Chinese reader, I want the Chinese text to be rendered by a font somebody chose, so that the product does not look different on my phone than on my laptop for no reason.
  2. As a Traditional Chinese reader, I want every character I type to render, including rare ones, so that a name or a technical term never comes out as a blank box.
  3. As a reader on a slow connection, I want the Chinese text to cost no download, so that reading is not gated on a font transfer.
  4. As the owner, I want no font requests to a third party, so that nobody is tracked page-by-page as a side effect of typography.
  5. As a SiaoHub visitor, I want Chinese repository descriptions and README bodies to render in a chosen font too, so that the content the product exists to display is not the part nobody styled.
  6. As a screen reader user, I want the document to declare the language it is actually written in, so that my reader pronounces it correctly.

Moving through the product

  1. As a visitor, I want every page to share one frame, so that moving between them does not feel like moving between separate websites.
  2. As a visitor, I want pages to begin at the top of the screen, so that a page reads as a page rather than as a lone card in empty space.
  3. As a visitor, I want every page to announce what it is with a heading, so that I know where I am without inferring it from the content.
  4. As a visitor, I want the content column to be the same width everywhere it should be, so that the product does not appear to change shape as I navigate.
  5. As a visitor, I want a footer, so that the page has an ending rather than stopping mid-air.
  6. As a visitor who arrived at AnswerHub, I want a way back to apps.siao.ai and siao.ai, so that I am not stranded inside one application.
  7. As a visitor on a phone, I want the same frame to work at that width, so that the product is not desktop-only by accident.
  8. As a developer adding a page later, I want the frame to come for free, so that a new page is consistent by default rather than by remembering.

Filling in forms

  1. As a user filling in a form, I want each field labelled above it, so that I can still see what a field is for after I start typing.
  2. As a screen reader user, I want each label bound to its field, so that my reader announces what I am editing.
  3. As a user, I want the file picker to look like part of the product, so that one control does not betray that the rest was styled and it was not.
  4. As a user, I want placeholder text to give me an example rather than repeat the label, so that it earns the space it takes.
  5. As a user who submits something invalid, I want to be told what is wrong, so that I can fix it rather than guess.

Waiting, failing, and getting lost

  1. As a user, I want a page that is loading to say so, so that I do not conclude my click did nothing and click again.
  2. As a user, I want a page that failed to say so, so that a blank screen is not the error message.
  3. As a user, I want a wrong URL to give me a page that looks like the product and offers me a way back, so that a typo does not feel like the site is broken.

Light and dark

  1. As a user, I want to choose light or dark myself, so that I am not bound to whatever my operating system is set to.
  2. As a user, I want my choice remembered, so that I do not re-pick it every visit.
  3. As a user, I want the page to render in my chosen theme immediately, so that I never see the wrong theme flash before the right one.
  4. As a user of both applications in the family, I want the theme control to work the same way in each, so that it is one product with one behaviour.
  5. As a developer, I want exactly one theming mechanism in the family, so that there is not a second one to keep in sync.

Reading the interface

  1. As a visitor, I want the product to use one word for the thing it stores, so that I do not wonder whether 作業 and 解答 are different things.
  2. As a visitor, I want that word to match the product's own name, so that the naming is coherent.
  3. As a visitor, I want icons that look the same on every device, so that the interface does not change character between my phone and my laptop.
  4. As a visitor, I want each page's browser tab to name that page, so that I can find it among a dozen open tabs.
  5. As a visitor sharing a link, I want the page to describe itself, so that what I paste is not identical for every page in the product.

Keeping it this way

  1. As a developer, I want a page that hand-writes its own height arithmetic to fail the test suite, so that the frame cannot be quietly bypassed.
  2. As a developer, I want a bare HTML form element to fail the test suite, so that the last one removed stays removed.
  3. As a developer, I want an emoji used as an icon to fail the test suite, so that platform-dependent glyphs do not creep back.
  4. As a developer, I want a page outside the shared shell to fail the test suite, so that consistency is enforced by machinery rather than by review attention.
  5. As the owner, I want accessibility checked on every page rather than one, so that the label work is verified rather than asserted.
  6. As the owner, I want the pages carrying the most layout decisions snapshotted in both themes, so that a later change that breaks them is caught rather than discovered.
  7. As the owner, I want the new conventions written into the design reference, so that the next application in the family inherits them instead of re-deciding.

Implementation Decisions

Testing Decisions

Out of Scope

Further Notes