Direct Answer

An AI coding workflow becomes useful when it produces reviewable behavior and evidence, not when it merely produces code quickly. This case study uses QubitTool's SBTI quiz to show the complete loop:

text
explore -> propose -> review -> apply -> verify -> archive

The implementation has multiple coupled contracts: dynamic routes, 30 scored questions, conditional questions, 15 dimensions, deterministic template matching, bilingual content, result URLs, share images, and browser-specific download behavior. That makes it a better Spec Coding example than a one-screen demo.

This article does not claim the site was built in a fixed number of hours. Elapsed time depends on the starting codebase, acceptance depth, review, rework, visual QA, accessibility, and release gates.

Key Takeaways

  • Use OpenSpec to make the intended behavior inspectable before code changes.
  • Keep product data and scoring rules separate from React views.
  • Treat every AI-generated patch as a proposal that still requires deterministic verification.
  • Test the scoring engine independently from the UI and result prose.
  • Define bilingual parity by IDs and behavior, not by identical sentence order.
  • Test canvas output as an image artifact; a correct DOM is insufficient evidence.
  • Archive only after implementation and verification agree with the reviewed change.

What the Current SBTI System Contains

The repository uses the Next.js Pages Router and a shared quiz registry. SBTI is one registered quiz implementation:

text
pages/quiz/[slug]/
  index.tsx
  test.tsx
  result/[code].tsx
  types.tsx
  about.tsx

src/quiz/
  quiz-definitions.ts
  sbti/
    data/questions.js
    data/personalities.js
    engine/scoring.js
    engine/matching.js
    components/
    pages/
    styles/

This structure separates generic routing from quiz-specific data, calculation, presentation, and style. The architecture is evidence from the current repository; it is not a universal template for every quiz.

Start with a Change, Not a Prompt

The current OpenSpec core workflow creates a reviewable change before implementation:

bash
npm install -g @fission-ai/openspec@latest
openspec init

In an AI assistant that supports OpenSpec skills:

text
/opsx:explore
/opsx:propose add-sbti-quiz
/opsx:apply add-sbti-quiz
/opsx:archive add-sbti-quiz

Command spelling varies by host, and the current OpenSpec documentation requires Node.js 20.19 or newer. Verify the installed version and generated instructions instead of copying a command from an old article.

The core distinction is:

Artifact Decision it should preserve
proposal.md why the change exists, scope, non-goals, impact
specs/<capability>/spec.md observable behavior and scenarios
design.md architecture, data ownership, trade-offs, migration
tasks.md implementation and verification work

Simple changes may omit a design document when the active schema permits it. Do not invent architecture ceremony just to fill a template.

Write the Behavioral Spec

The highest-value artifact is the spec. It should say what users and systems can observe:

markdown
## Requirement: The quiz produces a reproducible result

### Scenario: Complete regular-answer profile
- **WHEN** a user answers all 30 scored questions
- **THEN** the system computes exactly 15 dimension grades
- **AND** each grade is one of L, M, or H
- **AND** the matcher returns a valid result code

### Scenario: Missing scored answer
- **WHEN** any scored question is unanswered
- **THEN** the result calculation is not committed
- **AND** the user returns to the first incomplete question

### Scenario: Same answers in English and Chinese
- **WHEN** both locales submit the same option IDs
- **THEN** both produce the same dimension grades and result code

Notice what the scenarios do not specify: component names, CSS classes, or a preferred state library. Those are design or implementation choices unless they affect a public contract.

Make the Data Model Explicit

Questions and result definitions are executable content. Give them stable identities:

text
Question
  id
  dimension
  options[] -> stable value

Dimension
  id
  group
  localized name
  grade thresholds

ResultTemplate
  code
  15-grade pattern
  localized content
  related result codes

The current SBTI implementation has 30 scored questions, two per dimension. A branch question is always shown, and one follow-up is conditional. The regular matcher contains 25 templates; a fallback and a conditional route produce the other two displayed results.

The i18n files may translate labels and explanations, but they must not change question IDs, option values, dimension order, result codes, or route identity.

Specify the Scoring Contract

For each dimension, two values in the range 1 to 3 are summed:

Sum Grade
2-3 L
4 M
5-6 H

The 15 grades are converted to 0, 1, and 2 and compared with every regular template using Manhattan distance. The first minimum distance wins under the current implementation, so template order is part of tie behavior and must be tested.

Runnable Go Oracle

The following program checks the pair thresholds and produces the profile for the current CTRL template:

go
package main

import (
	"errors"
	"fmt"
	"strings"
)

func gradePair(left, right int) (byte, error) {
	if left < 1 || left > 3 || right < 1 || right > 3 {
		return 0, errors.New("answers must be between 1 and 3")
	}
	switch sum := left + right; {
	case sum <= 3:
		return 'L', nil
	case sum == 4:
		return 'M', nil
	default:
		return 'H', nil
	}
}

func buildProfile(answers [][2]int) (string, error) {
	if len(answers) != 15 {
		return "", errors.New("exactly 15 answer pairs are required")
	}
	grades := make([]byte, 0, 15)
	for _, pair := range answers {
		grade, err := gradePair(pair[0], pair[1])
		if err != nil {
			return "", err
		}
		grades = append(grades, grade)
	}
	groups := []string{
		string(grades[0:3]),
		string(grades[3:6]),
		string(grades[6:9]),
		string(grades[9:12]),
		string(grades[12:15]),
	}
	return strings.Join(groups, "-"), nil
}

func main() {
	high := [2]int{3, 3}
	mid := [2]int{2, 2}
	answers := [][2]int{
		high, high, high,
		high, mid, high,
		mid, high, high,
		high, high, high,
		mid, high, mid,
	}
	profile, err := buildProfile(answers)
	if err != nil {
		panic(err)
	}
	fmt.Println(profile)
}

Expected output:

text
HHH-HMH-MHH-HHH-MHM

This oracle validates the current calculation contract. It does not validate the psychological meaning of the questions or labels.

Design the Routing Boundary

The shared router should resolve a quiz definition before rendering a page. Unknown slugs and result codes require explicit behavior.

Route Contract
/quiz/[slug] registered quiz introduction
/quiz/[slug]/test answer flow for the same definition
/quiz/[slug]/result/[code] valid result or a controlled not-found state
/quiz/[slug]/types all registered result types
/quiz/[slug]/about methodology and limitations

Do not duplicate quiz metadata in every page. The registry should own slug, namespace, page components, result codes, and navigation identity. This prevents a new quiz from becoming a copy-and-edit fork.

Separate State from Presentation

A robust split is:

flowchart LR D["Versioned quiz data"] --> E["Pure scoring engine"] E --> R["Result identity"] R --> V["Localized result view"] R --> P["Share poster"] Q["Route registry"] --> V Q --> P T["Tests and fixtures"] --> E T --> V T --> P

The scoring engine should not read translations, browser dimensions, or canvas state. The result view should not recalculate grades. The poster should receive a committed result model rather than scraping visible DOM text.

Treat AI Coding as Controlled Execution

OpenSpec reduces ambiguity; it does not grant correctness. During apply:

  1. Give the agent a bounded file scope.
  2. Keep question and result data as a single source of truth.
  3. Require focused tests before broad UI polish.
  4. Review the diff after each ownership boundary.
  5. Reject unrelated refactors.
  6. Run the repository's real lint, build, and browser checks.
  7. Update the spec when the accepted behavior changes.

An AI agent that marks every task complete has only reported its own progress. Acceptance evidence must come from tests, rendered behavior, and review.

Poster Generation Is a Separate Product Surface

DOM-to-canvas libraries do not implement every CSS feature exactly like the browser renderer. Define a constrained poster contract:

  • fixed capture width and aspect behavior;
  • loaded local fonts and images before capture;
  • supported color formats and effects;
  • deterministic pixel-ratio policy;
  • no unsupported filters or external resources;
  • explicit timeout and failure state;
  • download behavior for normal browsers;
  • long-press guidance for browsers that block programmatic downloads.

Test the bitmap, not only the component. Use representative long names, narrow screens, high device-pixel ratios, and every result image family. A successful promise resolution is not proof that the pixels are correct.

Verification Matrix

Layer Required evidence
Data unique IDs, valid dimensions, option ranges, valid related codes
Scoring boundaries, exact matches, ties, fallback, conditional branch
Routing every registered page and result URL resolves
i18n same IDs and result code for equivalent answers
UI keyboard path, focus, readable labels, no layout overflow
Poster nonblank image, expected dimensions, assets loaded, save fallback
Privacy local calculation verified; any analytics or result submission documented separately
Release lint, build, focused tests, browser screenshots, rollback path

For the current project, shared routing and SEO are registered in quiz-definitions.ts, while SBTI owns its questions, templates, scoring, matching, components, and styles under src/quiz/sbti/.

What the Original Case Study Got Wrong

Several attractive claims are not useful engineering evidence:

Claim Why it fails Better record
“Built in half a day” scope and verification are unknown task timeline with review and rework
“AI translated everything perfectly” no bilingual acceptance evidence ID parity plus language review
“The agent solved the canvas bug” outcome and regression surface are missing failing fixture, patch, screenshot checks
“Independent routes make it viral” route shape does not prove distribution share preview, canonical, click and retention data
“Commit often” commit count does not establish quality coherent checkpoints with passing gates

The reusable lesson is not speed. It is that an explicit behavior contract lets humans and agents disagree early, test the same outcome, and resume without relying on chat memory.

Frequently Asked Questions

Should every feature use OpenSpec?

No. A tiny deterministic edit may not justify a full change folder. Use it when behavior spans components, data, migrations, security, compatibility, or several sessions and reviewers.

Can the spec contain implementation details?

Only when they are externally required or necessary to preserve a decision. Put observable behavior in specs and architecture trade-offs in design; keep routine file-level steps in tasks.

How do you prevent scoring and translations from drifting?

Use stable IDs and values as the shared identity, then validate both locale files against the same schema. Translate text, not dimensions, option weights, result codes, or routes.

Should visual polish happen after the spec?

Visual behavior belongs in the spec when it affects readability, interaction, accessibility, or sharing. Fine-grained styling can iterate during apply, but its acceptance viewport and output must remain explicit.

When is the change ready to archive?

When all required behavior is implemented, every task is resolved, automated checks pass, browser evidence covers the risky paths, and the reviewed implementation matches the current artifacts.

Conclusion

The SBTI case study shows what Spec Coding should make durable: behavior, ownership, failure semantics, and evidence. OpenSpec creates the review loop, but the engineering value comes from a typed data model, pure scoring contract, locale parity, image-level poster checks, and release gates that remain valid when the model or coding tool changes.

The finished behavior is visible in the SBTI quiz. For the broader method, read the OpenSpec tutorial and Spec Coding guide.

Sources