diff --git a/.github/workflows/quiz-app2-build.yml b/.github/workflows/quiz-app-build.yml similarity index 62% rename from .github/workflows/quiz-app2-build.yml rename to .github/workflows/quiz-app-build.yml index 6f7e12d..359feda 100644 --- a/.github/workflows/quiz-app2-build.yml +++ b/.github/workflows/quiz-app-build.yml @@ -1,27 +1,27 @@ -name: quiz-app2 / Test & Build +name: quiz-app / Test & Build on: push: paths: - - "quiz-app2/**" - - ".github/workflows/quiz-app2-build.yml" - - ".github/workflows/quiz-app2-pages.yml" + - "quiz-app/**" + - ".github/workflows/quiz-app-build.yml" + - ".github/workflows/quiz-app-pages.yml" workflow_dispatch: permissions: contents: read concurrency: - group: quiz-app2-ci-${{ github.ref }} + group: quiz-app-ci-${{ github.ref }} cancel-in-progress: true jobs: build: - name: Test and build quiz-app2 + name: Test and build quiz-app runs-on: ubuntu-latest defaults: run: - working-directory: quiz-app2 + working-directory: quiz-app steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -30,7 +30,7 @@ jobs: with: node-version: 22 cache: npm - cache-dependency-path: quiz-app2/package-lock.json + cache-dependency-path: quiz-app/package-lock.json - name: Install dependencies run: npm ci diff --git a/.github/workflows/quiz-app-github-pages.yml b/.github/workflows/quiz-app-github-pages.yml deleted file mode 100644 index 72312b9..0000000 --- a/.github/workflows/quiz-app-github-pages.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: quiz-app / GitHub Pages - -on: - push: - branches: - - main - paths: - - "quiz-app/public/**" - - "quiz-app/quizzes/**" - - "quiz-app/cmd/generatequizdata/**" - - "quiz-app/internal/quizdata/**" - - "quiz-app/Makefile" - - "quiz-app/go.mod" - - "quiz-app/go.sum" - - ".github/workflows/quiz-app-github-pages.yml" - -permissions: - contents: read - -concurrency: - group: quiz-app-github-pages - cancel-in-progress: true - -jobs: - build: - name: Build GitHub Pages site - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 - with: - go-version-file: quiz-app/go.mod - - - name: Build public site - working-directory: quiz-app - env: - QUIZ_TELEMETRY_ENDPOINT: ${{ vars.QUIZ_TELEMETRY_ENDPOINT }} - QUIZ_PREVIEW_UNLOCK_CODE: ${{ vars.QUIZ_PREVIEW_UNLOCK_CODE }} - run: | - make build-pages - node <<'EOF' - const fs = require('fs'); - const vm = require('vm'); - const sandbox = { window: {} }; - vm.createContext(sandbox); - vm.runInContext(fs.readFileSync('public/quiz-config.js', 'utf8'), sandbox); - - const defaults = sandbox.window.__QUIZ_APP_CONFIG__ || {}; - const config = { - ...defaults, - ...(process.env.QUIZ_TELEMETRY_ENDPOINT ? { telemetryEndpoint: process.env.QUIZ_TELEMETRY_ENDPOINT } : {}), - ...(process.env.QUIZ_PREVIEW_UNLOCK_CODE ? { previewUnlockCode: process.env.QUIZ_PREVIEW_UNLOCK_CODE } : {}), - }; - fs.writeFileSync( - 'gh-pages-dist/quiz-config.js', - `window.__QUIZ_APP_CONFIG__ = Object.assign(window.__QUIZ_APP_CONFIG__ || {}, ${JSON.stringify(config, null, 2)});\n`, - ); - EOF - - - uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4 - with: - path: quiz-app/gh-pages-dist - - deploy: - name: Deploy GitHub Pages site - needs: build - if: ${{ false }} - runs-on: ubuntu-latest - permissions: - pages: write - id-token: write - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - - steps: - - id: deployment - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4 diff --git a/.github/workflows/quiz-app-normal-github-pages.yml b/.github/workflows/quiz-app-normal-github-pages.yml deleted file mode 100644 index f6e2bde..0000000 --- a/.github/workflows/quiz-app-normal-github-pages.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: quiz-app-normal / GitHub Pages - -on: - push: - branches: - - main - paths: - - "quiz-app-normal/public/**" - - "quiz-app-normal/quizzes/**" - - "quiz-app-normal/cmd/generatequizdata/**" - - "quiz-app-normal/internal/quizdata/**" - - "quiz-app-normal/Makefile" - - "quiz-app-normal/go.mod" - - "quiz-app-normal/go.sum" - - ".github/workflows/quiz-app-normal-github-pages.yml" - -permissions: - contents: read - -concurrency: - group: quiz-app-normal-github-pages - cancel-in-progress: true - -jobs: - build: - name: Build GitHub Pages site - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 - with: - go-version-file: quiz-app-normal/go.mod - - - name: Build public site - working-directory: quiz-app-normal - env: - QUIZ_APP_NORMAL_TELEMETRY_ENDPOINT: ${{ vars.QUIZ_APP_NORMAL_TELEMETRY_ENDPOINT }} - QUIZ_APP_NORMAL_PREVIEW_UNLOCK_CODE: ${{ vars.QUIZ_APP_NORMAL_PREVIEW_UNLOCK_CODE }} - run: | - make build-pages - node <<'EOF' - const fs = require('fs'); - const vm = require('vm'); - const sandbox = { window: {} }; - vm.createContext(sandbox); - vm.runInContext(fs.readFileSync('public/quiz-config.js', 'utf8'), sandbox); - - const defaults = sandbox.window.__QUIZ_APP_CONFIG__ || {}; - const config = { - ...defaults, - ...(process.env.QUIZ_APP_NORMAL_TELEMETRY_ENDPOINT ? { telemetryEndpoint: process.env.QUIZ_APP_NORMAL_TELEMETRY_ENDPOINT } : {}), - ...(process.env.QUIZ_APP_NORMAL_PREVIEW_UNLOCK_CODE ? { previewUnlockCode: process.env.QUIZ_APP_NORMAL_PREVIEW_UNLOCK_CODE } : {}), - }; - fs.writeFileSync( - 'gh-pages-dist/quiz-config.js', - `window.__QUIZ_APP_CONFIG__ = Object.assign(window.__QUIZ_APP_CONFIG__ || {}, ${JSON.stringify(config, null, 2)});\n`, - ); - EOF - - - uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4 - with: - path: quiz-app-normal/gh-pages-dist - - deploy: - name: Deploy GitHub Pages site - needs: build - if: ${{ false }} - runs-on: ubuntu-latest - permissions: - pages: write - id-token: write - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - - steps: - - id: deployment - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4 diff --git a/.github/workflows/quiz-app-normal-test.yml b/.github/workflows/quiz-app-normal-test.yml deleted file mode 100644 index 5e4dd40..0000000 --- a/.github/workflows/quiz-app-normal-test.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: quiz-app-normal / Test - -on: - pull_request: - paths: - - "quiz-app-normal/**/*.go" - - "quiz-app-normal/public/**" - - "quiz-app-normal/quizzes/**" - - "quiz-app-normal/cmd/generatequizdata/**" - - "quiz-app-normal/test/**" - - "quiz-app-normal/go.mod" - - "quiz-app-normal/go.sum" - - "quiz-app-normal/Makefile" - - ".github/workflows/quiz-app-normal-test.yml" - - ".github/workflows/quiz-app-normal-github-pages.yml" - -permissions: - contents: read - -jobs: - test-go: - name: Go Tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version-file: quiz-app-normal/go.mod - cache-dependency-path: quiz-app-normal/go.sum - - - name: Run tests - working-directory: quiz-app-normal - run: go test ./... - - static-site-checks: - name: Browser & Pages Checks - runs-on: ubuntu-latest - defaults: - run: - working-directory: quiz-app-normal - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version-file: quiz-app-normal/go.mod - cache-dependency-path: quiz-app-normal/go.sum - - - name: Run browser tests - run: make test-browser - - - name: Build GitHub Pages site - run: make build-pages diff --git a/.github/workflows/quiz-app2-pages.yml b/.github/workflows/quiz-app-pages.yml similarity index 71% rename from .github/workflows/quiz-app2-pages.yml rename to .github/workflows/quiz-app-pages.yml index 5c2d820..0a31cea 100644 --- a/.github/workflows/quiz-app2-pages.yml +++ b/.github/workflows/quiz-app-pages.yml @@ -1,13 +1,13 @@ -name: quiz-app2 / Deploy Pages +name: quiz-app / Deploy Pages on: push: branches: - main paths: - - "quiz-app2/**" - - ".github/workflows/quiz-app2-build.yml" - - ".github/workflows/quiz-app2-pages.yml" + - "quiz-app/**" + - ".github/workflows/quiz-app-build.yml" + - ".github/workflows/quiz-app-pages.yml" workflow_dispatch: permissions: @@ -16,27 +16,27 @@ permissions: id-token: write concurrency: - group: quiz-app2-pages + group: quiz-app-pages cancel-in-progress: true jobs: build: - name: Build quiz-app2 Pages artifact + name: Build quiz-app Pages artifact runs-on: ubuntu-latest defaults: run: - working-directory: quiz-app2 + working-directory: quiz-app steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 22 cache: npm - cache-dependency-path: quiz-app2/package-lock.json + cache-dependency-path: quiz-app/package-lock.json - - uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5 + - uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5 - name: Install dependencies run: npm ci @@ -54,12 +54,12 @@ jobs: cp dist/index.html dist/404.html touch dist/.nojekyll - - uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4 + - uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4 with: - path: quiz-app2/dist + path: quiz-app/dist deploy: - name: Deploy quiz-app2 to GitHub Pages + name: Deploy quiz-app to GitHub Pages needs: build runs-on: ubuntu-latest environment: @@ -68,4 +68,4 @@ jobs: steps: - id: deployment - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4 + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4 diff --git a/.github/workflows/quiz-app-test.yml b/.github/workflows/quiz-app-test.yml deleted file mode 100644 index daa2ef3..0000000 --- a/.github/workflows/quiz-app-test.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: quiz-app / Test - -on: - pull_request: - paths: - - "quiz-app/**/*.go" - - "quiz-app/public/**" - - "quiz-app/quizzes/**" - - "quiz-app/cmd/generatequizdata/**" - - "quiz-app/test/**" - - "quiz-app/go.mod" - - "quiz-app/go.sum" - - "quiz-app/Makefile" - - ".github/workflows/quiz-app-test.yml" - - ".github/workflows/quiz-app-github-pages.yml" - -permissions: - contents: read - -jobs: - test-go: - name: Go Tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version-file: quiz-app/go.mod - cache-dependency-path: quiz-app/go.sum - - - name: Run tests - working-directory: quiz-app - run: go test ./... - - static-site-checks: - name: Browser & Pages Checks - runs-on: ubuntu-latest - defaults: - run: - working-directory: quiz-app - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version-file: quiz-app/go.mod - cache-dependency-path: quiz-app/go.sum - - - name: Run browser tests - run: make test-browser - - - name: Build GitHub Pages site - run: make build-pages diff --git a/README.md b/README.md index 902bd37..2038204 100644 --- a/README.md +++ b/README.md @@ -1,115 +1,20 @@ # 2026-codelab -GoCon 2026 コードラボ用のクイズリポジトリです。 -各自が Go に関する 4 択クイズを作成し、ブラウザでプレビューできます。 +GoCon 2026 CodeLab用のクイズリポジトリです。 -## 必要なもの - -- Go 1.26 以上 (go.mod の toolchain で固定しています) -- `git config user.name` に GitHub ユーザーネームが設定されていること - -## はじめかた - -### 1. リポジトリをクローン - -```sh -git clone https://github.com/GoCon/2026-codelab.git -cd 2026-codelab -``` - -### 2. 自分のクイズディレクトリを作成 - -```sh -./scripts/setup.sh -``` - -`git config user.name` から自動でユーザーネームを取得し、`quizzes//` にテンプレートが生成されます。 - -### 3. クイズを編集 - -`quizzes//quiz.yaml` を編集してください。テンプレートにサンプル問題とコメントが入っているので参考にしてください。 - -### 4. プレビューで確認 - -```sh -go run ./cmd/preview quizzes//quiz.yaml -``` - -http://localhost:8080 をブラウザで開くとクイズをプレビューできます。 -YAML やコードファイルを編集した後、ブラウザをリロードすると即反映されます。 - -### 5. PR を出す - -クイズが完成したら、自分のユーザーネームのディレクトリのみを含む PR を作成してください。 - -### Playground URL を埋め込む - -answer コードを Go Playground に反映して `answer_code_play_ref` を自動更新したいときは、次を実行してください。 - -```sh -go run ./scripts/sync_answer_code_play_refs ``` - -特定のクイズだけ更新したい場合は、ディレクトリまたは `quiz.yaml` を引数に渡せます。 - -```sh -go run ./scripts/sync_answer_code_play_refs quizzes/ -``` - -## クイズの書き方 - -### ディレクトリ構成 - -``` -quizzes// - quiz.yaml # クイズ定義 - code/ - q1.go # コードファイル (quiz.yaml から参照) - q2_question.go - q2_answer.go -``` - -### 問題のパターン - -#### テキストのみの問題 - -```yaml -questions: - - text: "Go でスライスの長さを取得する組み込み関数は?" - choices: - - "size()" - - "count()" - - "len()" - - "length()" - answer: 2 - explanation: "len() は Go の組み込み関数です。" - answer_code_ref: "code/q1.go" -``` - -#### コードを読ませる問題 - -```yaml -questions: - - text: "このコードの出力は何?" - question_code_ref: "code/q2_question.go" - choices: - - "5" - - "hello" - - "コンパイルエラー" - - "0" - answer: 0 - explanation: "len() は文字列のバイト数を返します。" - answer_code_ref: "code/q2_answer.go" +├── draft/ # プロトタイプ +│ └── quizzes/ # CodeLabメンバーが作成したクイズ一覧 +└── quiz-app/ # メインのクイズアプリ + ├── src/ + │ ├── assets/ # ロゴや画像アセット + │ ├── components/ + │ │ ├── games/ # クイズ形式(FillBlankTapGame, ChoiceSelectGame) + │ │ └── ui/ # 共通UI(StageOutputPanel, PressButton) + │ └── data/ # クイズのデータ(stages.toml, stages.ts) + └── test/ # 堅牢なUI統合テスト(Vitest) ``` -### フィールド一覧 +CodeLabの実行の仕方、開発方法はCodeLabのREADMEを参照してください。 -| フィールド | 必須 | 説明 | -|---|---|---| -| `text` | o | 問題文 | -| `question_code_ref` | | 問題文に表示するコードファイル (quiz.yaml からの相対パス) | -| `choices` | o | 選択肢 (4 つ) | -| `answer` | o | 正解の index (0-indexed: 0 が最初の選択肢) | -| `explanation` | o | 解説 (正解・不正解どちらでも表示されます) | -| `answer_code_ref` | | 正解時のみ表示するコードファイル (quiz.yaml からの相対パス) | -| `answer_code_play_ref` | | Go Playground 上の answer コード URL (`go run ./scripts/sync_answer_code_play_refs` で自動更新) | +[README](quiz-app/README.md) diff --git a/draft/README.md b/draft/README.md new file mode 100644 index 0000000..50e12c4 --- /dev/null +++ b/draft/README.md @@ -0,0 +1,114 @@ +# 2026-codelab + +GoCon 2026 コードラボ用のクイズリポジトリです。 + +## 必要なもの + +- Go 1.26 以上 (go.mod の toolchain で固定しています) +- `git config user.name` に GitHub ユーザーネームが設定されていること + +## はじめかた + +### 1. リポジトリをクローン + +```sh +git clone https://github.com/GoCon/2026-codelab.git +cd 2026-codelab +``` + +### 2. 自分のクイズディレクトリを作成 + +```sh +./scripts/setup.sh +``` + +`git config user.name` から自動でユーザーネームを取得し、`quizzes//` にテンプレートが生成されます。 + +### 3. クイズを編集 + +`quizzes//quiz.yaml` を編集してください。テンプレートにサンプル問題とコメントが入っているので参考にしてください。 + +### 4. プレビューで確認 + +```sh +go run ./cmd/preview quizzes//quiz.yaml +``` + +http://localhost:8080 をブラウザで開くとクイズをプレビューできます。 +YAML やコードファイルを編集した後、ブラウザをリロードすると即反映されます。 + +### 5. PR を出す + +クイズが完成したら、自分のユーザーネームのディレクトリのみを含む PR を作成してください。 + +### Playground URL を埋め込む + +answer コードを Go Playground に反映して `answer_code_play_ref` を自動更新したいときは、次を実行してください。 + +```sh +go run ./scripts/sync_answer_code_play_refs +``` + +特定のクイズだけ更新したい場合は、ディレクトリまたは `quiz.yaml` を引数に渡せます。 + +```sh +go run ./scripts/sync_answer_code_play_refs quizzes/ +``` + +## クイズの書き方 + +### ディレクトリ構成 + +``` +quizzes// + quiz.yaml # クイズ定義 + code/ + q1.go # コードファイル (quiz.yaml から参照) + q2_question.go + q2_answer.go +``` + +### 問題のパターン + +#### テキストのみの問題 + +```yaml +questions: + - text: "Go でスライスの長さを取得する組み込み関数は?" + choices: + - "size()" + - "count()" + - "len()" + - "length()" + answer: 2 + explanation: "len() は Go の組み込み関数です。" + answer_code_ref: "code/q1.go" +``` + +#### コードを読ませる問題 + +```yaml +questions: + - text: "このコードの出力は何?" + question_code_ref: "code/q2_question.go" + choices: + - "5" + - "hello" + - "コンパイルエラー" + - "0" + answer: 0 + explanation: "len() は文字列のバイト数を返します。" + answer_code_ref: "code/q2_answer.go" +``` + +### フィールド一覧 + +| フィールド | 必須 | 説明 | +|---|---|---| +| `text` | o | 問題文 | +| `question_code_ref` | | 問題文に表示するコードファイル (quiz.yaml からの相対パス) | +| `choices` | o | 選択肢 (4 つ) | +| `answer` | o | 正解の index (0-indexed: 0 が最初の選択肢) | +| `explanation` | o | 解説 (正解・不正解どちらでも表示されます) | +| `answer_code_ref` | | 正解時のみ表示するコードファイル (quiz.yaml からの相対パス) | +| `answer_code_play_ref` | | Go Playground 上の answer コード URL (`go run ./scripts/sync_answer_code_play_refs` で自動更新) | diff --git a/cmd/preview/main.go b/draft/cmd/preview/main.go similarity index 100% rename from cmd/preview/main.go rename to draft/cmd/preview/main.go diff --git a/cmd/preview/main_test.go b/draft/cmd/preview/main_test.go similarity index 100% rename from cmd/preview/main_test.go rename to draft/cmd/preview/main_test.go diff --git a/cmd/preview/templates/index.html b/draft/cmd/preview/templates/index.html similarity index 100% rename from cmd/preview/templates/index.html rename to draft/cmd/preview/templates/index.html diff --git a/internal/playground/client.go b/draft/internal/playground/client.go similarity index 100% rename from internal/playground/client.go rename to draft/internal/playground/client.go diff --git a/internal/playground/client_test.go b/draft/internal/playground/client_test.go similarity index 100% rename from internal/playground/client_test.go rename to draft/internal/playground/client_test.go diff --git a/internal/quizcode/normalize.go b/draft/internal/quizcode/normalize.go similarity index 100% rename from internal/quizcode/normalize.go rename to draft/internal/quizcode/normalize.go diff --git a/internal/quizcode/normalize_test.go b/draft/internal/quizcode/normalize_test.go similarity index 100% rename from internal/quizcode/normalize_test.go rename to draft/internal/quizcode/normalize_test.go diff --git a/quiz-app-normal/Makefile b/draft/quiz-app/Makefile similarity index 100% rename from quiz-app-normal/Makefile rename to draft/quiz-app/Makefile diff --git a/quiz-app-normal/README.md b/draft/quiz-app/README.md similarity index 81% rename from quiz-app-normal/README.md rename to draft/quiz-app/README.md index 1db9d60..ab2c823 100644 --- a/quiz-app-normal/README.md +++ b/draft/quiz-app/README.md @@ -1,4 +1,4 @@ -# quiz-app-normal +# quiz-app Go Conference 2026 のコードラボ向け Go クイズアプリです。 公開 UI は GitHub Pages に静的配信し、回答ログと問題ごとの正答率集計は Google Apps Script + Google スプレッドシートで扱います。 @@ -8,14 +8,16 @@ Go Conference 2026 のコードラボ向け Go クイズアプリです。 - ランダムに選ばれた 5 問を出題 - ヘッダー右上の地球儀メニューから日本語 / English を切り替え可能 - 問題文・選択肢・解説・タイトルも選択言語に合わせて切り替え -- `quizes.yaml` の `mode` が `normal` / `both` の問題だけを出題 +- 問題ごとに `normal` / `extra` / `both` の出題プールを持てる - 問題・選択肢・Go コードのシンタックスハイライト表示 - 選択肢順は出題ごとにランダムシャッフル - 回答後に正誤・解説・解答コード・Go Playground リンクを表示 - 全問正解でスペシャルページへのリンクを表示 - 回答開始からの経過秒数を計測し、全問正解時はクリアタイム表示とニックネーム送信が可能 +- エクストラモードで未出問題へ継続挑戦、累計スコアを表示 - 問題プレビューモードを公開 UI に統合 - フッターを 10 回タップすると hidden Keyword ポップアップが開き、Keyword で問題プレビューモードを開ける + - またはエクストラモード全問正解で解放 - 回答イベントを Google Apps Script に送信し、スプレッドシートで問題ごとの集計を更新 ## 技術スタック @@ -31,7 +33,7 @@ Go Conference 2026 のコードラボ向け Go クイズアプリです。 ## ディレクトリ構成 ```text -quiz-app-normal/ +quiz-app/ ├── cmd/ │ ├── generatequizdata/ # quizes.yaml から quiz-data.js を生成 │ └── local/ # ローカル開発用サーバー @@ -56,9 +58,9 @@ quiz-app-normal/ > Go 1.25 以上が必要です。 ```bash -cd quiz-app-normal +cd quiz-app make local -# http://localhost:8789 +# http://localhost:8788 ``` `make local` は `public/` を配信しつつ、`/quiz-data.js` を動的生成します。 @@ -67,7 +69,7 @@ make local ## GitHub Pages 用の静的ビルド ```bash -cd quiz-app-normal +cd quiz-app make build-pages ``` @@ -93,7 +95,7 @@ Apps Script は `Logs`、`Summary`、`Attempts`、`PerfectScores` の 4 シー ### 2. Apps Script を作成 1. スプレッドシートで **拡張機能 > Apps Script** を開く -2. `quiz-app-normal/gas/telemetry.gs` の内容を貼り付ける +2. `quiz-app/gas/telemetry.gs` の内容を貼り付ける 3. **デプロイ > 新しいデプロイ** 4. 種類を **ウェブアプリ** にする 5. 実行ユーザーを **自分** @@ -141,7 +143,7 @@ Apps Script は `Logs`、`Summary`、`Attempts`、`PerfectScores` の 4 シー "elapsed_seconds": 42, "correct_count": 4, "completed_at": "2026-04-15T12:39:56.000Z", - "mode": "normal", + "mode": "extra", "session_id": "same-browser-session-id", "attempt_id": "single-quiz-run-id" } @@ -150,23 +152,23 @@ Apps Script は `Logs`、`Summary`、`Attempts`、`PerfectScores` の 4 シー - `Logs` シートには全回答が追記され、`session_id`、`attempt_id`、各回答時点の `elapsed_seconds`、各問題にかかった `question_elapsed_seconds` に加えて、元の選択肢 index / 表示 index / 選択肢テキスト / `choice_order` が保存されます - `Summary` シートには問題ごとの **初回回答時の正答率** と **最終回答時の正答率**、および初回/最終回答時点の平均所要秒数が再計算されます - `Attempts` シートには各 `attempt_id` ごとの回答数、完走有無、最後に到達した問題番号、総経過秒数がまとまり、途中離脱の分析に使えます -- `PerfectScores` シートには normal の全問正解時に送信されたニックネーム、完走タイム、正解数、回答完了日時、`mode`(常に `normal`)、`attempt_id` が保存されます +- `PerfectScores` シートには normal の全問正解時と extra の完走時に送信されたニックネーム、完走タイム、正解数、回答完了日時、`mode`(`normal` / `extra`)、`attempt_id` が保存され、**extra → normal → 正解数の多い順 → 完走タイムの短い順** のランキングになるよう再構築されます ## GitHub Pages デプロイについて GitHub Pages 向けの静的成果物は `make build-pages` で生成できます。 -`.github/workflows/quiz-app-normal-github-pages.yml` では **build job と artifact upload だけ動かし、deploy job は公開日まで skip** しています。 +`.github/workflows/quiz-app-github-pages.yml` では **build job と artifact upload だけ動かし、deploy job は公開日まで skip** しています。 リポジトリの GitHub Pages は公開日まで有効化しない前提のため、workflow は `actions/configure-pages` を呼ばずに静的成果物の build 確認だけを行います。 workflow では次の Variables を使います。 | Variable 名 | 必須 | 説明 | |---|---|---| -| `QUIZ_APP_NORMAL_TELEMETRY_ENDPOINT` | 任意 | Apps Script の Web アプリ URL(未設定時は記録送信を無効化) | -| `QUIZ_APP_NORMAL_PREVIEW_UNLOCK_CODE` | 任意 | フッター 10 タップ後に開く hidden Keyword ポップアップで使うコード(未設定時は `gofar,gotogether`) | +| `QUIZ_TELEMETRY_ENDPOINT` | 任意 | Apps Script の Web アプリ URL(未設定時は `public/quiz-config.js` の既定値を使用) | +| `QUIZ_PREVIEW_UNLOCK_CODE` | 任意 | フッター 10 タップ後に開く hidden Keyword ポップアップで使うコード(未設定時は `gofar,gotogether`) | -- `QUIZ_APP_NORMAL_TELEMETRY_ENDPOINT` が空でも静的サイト自体はビルドでき、既定では記録送信は無効のままです -- `QUIZ_APP_NORMAL_PREVIEW_UNLOCK_CODE` を未設定のまま使う場合、既定の hidden Keyword は `gofar,gotogether` です +- `QUIZ_TELEMETRY_ENDPOINT` が空でも静的サイト自体はビルドでき、`public/quiz-config.js` に含まれる既定の Apps Script URL が使われます +- `QUIZ_PREVIEW_UNLOCK_CODE` を未設定のまま使う場合、既定の hidden Keyword は `gofar,gotogether` です workflow の build job は `public/quiz-config.js` の既定値を読み込み、Actions Variables が設定されている項目だけ `gh-pages-dist/quiz-config.js` へ上書きします。 @@ -175,7 +177,7 @@ workflow の build job は `public/quiz-config.js` の既定値を読み込み ## テスト ```bash -cd quiz-app-normal +cd quiz-app go test ./... make test-browser ``` @@ -208,7 +210,7 @@ func main() { title_en: "Question title" text: "問題文" text_en: "Question text" - mode: "normal" + mode: "extra" question_code_ref: "code/author_q1_question.go" choices: - "選択肢 A" @@ -229,11 +231,11 @@ func main() { `*_en` フィールドを省略した場合は日本語の値がそのまま英語表示にも使われます。公開用の問題は日本語・英語の両方を埋める前提です。 -`mode` は省略可能です。値は次の 3 つです。`quiz-app-normal` では `extra` の問題は配信対象から除外されます。 +`mode` は省略可能です。値は次の 3 つです。 -- `both`(既定値): `quiz-app-normal` でも出題候補に入る -- `normal`: `quiz-app-normal` で出題する -- `extra`: 共有データには残せるが、`quiz-app-normal` では出題しない +- `both`(既定値): ノーマル / エクストラの両方で出題候補に入る +- `normal`: ノーマルモードのみで出題する +- `extra`: エクストラモードのみで出題する `answer` は **元の choices 配列に対する正解 index** を指定します。公開 UI 側では表示時に choices をシャッフルしますが、telemetry には元 index と表示 index の両方が送られます。 diff --git a/quiz-app/cmd/generatequizdata/main.go b/draft/quiz-app/cmd/generatequizdata/main.go similarity index 100% rename from quiz-app/cmd/generatequizdata/main.go rename to draft/quiz-app/cmd/generatequizdata/main.go diff --git a/quiz-app/cmd/local/main.go b/draft/quiz-app/cmd/local/main.go similarity index 100% rename from quiz-app/cmd/local/main.go rename to draft/quiz-app/cmd/local/main.go diff --git a/quiz-app-normal/gas/telemetry.gs b/draft/quiz-app/gas/telemetry.gs similarity index 100% rename from quiz-app-normal/gas/telemetry.gs rename to draft/quiz-app/gas/telemetry.gs diff --git a/quiz-app/go.mod b/draft/quiz-app/go.mod similarity index 100% rename from quiz-app/go.mod rename to draft/quiz-app/go.mod diff --git a/quiz-app-normal/go.sum b/draft/quiz-app/go.sum similarity index 100% rename from quiz-app-normal/go.sum rename to draft/quiz-app/go.sum diff --git a/quiz-app/internal/quizdata/quizdata.go b/draft/quiz-app/internal/quizdata/quizdata.go similarity index 100% rename from quiz-app/internal/quizdata/quizdata.go rename to draft/quiz-app/internal/quizdata/quizdata.go diff --git a/quiz-app/internal/quizdata/quizdata_test.go b/draft/quiz-app/internal/quizdata/quizdata_test.go similarity index 100% rename from quiz-app/internal/quizdata/quizdata_test.go rename to draft/quiz-app/internal/quizdata/quizdata_test.go diff --git a/quiz-app-normal/internal/quizhandler/handler.go b/draft/quiz-app/internal/quizhandler/handler.go similarity index 100% rename from quiz-app-normal/internal/quizhandler/handler.go rename to draft/quiz-app/internal/quizhandler/handler.go diff --git a/quiz-app/internal/quizhandler/handler_test.go b/draft/quiz-app/internal/quizhandler/handler_test.go similarity index 100% rename from quiz-app/internal/quizhandler/handler_test.go rename to draft/quiz-app/internal/quizhandler/handler_test.go diff --git a/quiz-app-normal/public/assets/extra-mode.css b/draft/quiz-app/public/assets/extra-mode.css similarity index 100% rename from quiz-app-normal/public/assets/extra-mode.css rename to draft/quiz-app/public/assets/extra-mode.css diff --git a/quiz-app-normal/public/assets/extra-mode.js b/draft/quiz-app/public/assets/extra-mode.js similarity index 100% rename from quiz-app-normal/public/assets/extra-mode.js rename to draft/quiz-app/public/assets/extra-mode.js diff --git a/quiz-app-normal/public/assets/go-conference-2026-logo.svg b/draft/quiz-app/public/assets/go-conference-2026-logo.svg similarity index 100% rename from quiz-app-normal/public/assets/go-conference-2026-logo.svg rename to draft/quiz-app/public/assets/go-conference-2026-logo.svg diff --git a/quiz-app-normal/public/assets/progress-gopher.png b/draft/quiz-app/public/assets/progress-gopher.png similarity index 100% rename from quiz-app-normal/public/assets/progress-gopher.png rename to draft/quiz-app/public/assets/progress-gopher.png diff --git a/quiz-app/public/index.html b/draft/quiz-app/public/index.html similarity index 100% rename from quiz-app/public/index.html rename to draft/quiz-app/public/index.html diff --git a/quiz-app/public/preview/index.html b/draft/quiz-app/public/preview/index.html similarity index 100% rename from quiz-app/public/preview/index.html rename to draft/quiz-app/public/preview/index.html diff --git a/quiz-app/public/quiz-config.js b/draft/quiz-app/public/quiz-config.js similarity index 100% rename from quiz-app/public/quiz-config.js rename to draft/quiz-app/public/quiz-config.js diff --git a/quiz-app-normal/quizzes/code/Rtosshy_q1_answer.go b/draft/quiz-app/quizzes/code/Rtosshy_q1_answer.go similarity index 100% rename from quiz-app-normal/quizzes/code/Rtosshy_q1_answer.go rename to draft/quiz-app/quizzes/code/Rtosshy_q1_answer.go diff --git a/quiz-app-normal/quizzes/code/Rtosshy_q1_question.go b/draft/quiz-app/quizzes/code/Rtosshy_q1_question.go similarity index 100% rename from quiz-app-normal/quizzes/code/Rtosshy_q1_question.go rename to draft/quiz-app/quizzes/code/Rtosshy_q1_question.go diff --git a/quiz-app-normal/quizzes/code/Rtosshy_q3_question.go b/draft/quiz-app/quizzes/code/Rtosshy_q3_question.go similarity index 100% rename from quiz-app-normal/quizzes/code/Rtosshy_q3_question.go rename to draft/quiz-app/quizzes/code/Rtosshy_q3_question.go diff --git a/quiz-app-normal/quizzes/code/hasegawa_itsuki_q3_question.go b/draft/quiz-app/quizzes/code/hasegawa_itsuki_q3_question.go similarity index 100% rename from quiz-app-normal/quizzes/code/hasegawa_itsuki_q3_question.go rename to draft/quiz-app/quizzes/code/hasegawa_itsuki_q3_question.go diff --git a/quiz-app-normal/quizzes/code/makki_d_q2_answer.go b/draft/quiz-app/quizzes/code/makki_d_q2_answer.go similarity index 100% rename from quiz-app-normal/quizzes/code/makki_d_q2_answer.go rename to draft/quiz-app/quizzes/code/makki_d_q2_answer.go diff --git a/quiz-app-normal/quizzes/code/makki_d_q2_question.go b/draft/quiz-app/quizzes/code/makki_d_q2_question.go similarity index 100% rename from quiz-app-normal/quizzes/code/makki_d_q2_question.go rename to draft/quiz-app/quizzes/code/makki_d_q2_question.go diff --git a/quiz-app-normal/quizzes/code/makki_d_q3_question.go b/draft/quiz-app/quizzes/code/makki_d_q3_question.go similarity index 100% rename from quiz-app-normal/quizzes/code/makki_d_q3_question.go rename to draft/quiz-app/quizzes/code/makki_d_q3_question.go diff --git a/quiz-app-normal/quizzes/code/ryokotmng_q1.go b/draft/quiz-app/quizzes/code/ryokotmng_q1.go similarity index 100% rename from quiz-app-normal/quizzes/code/ryokotmng_q1.go rename to draft/quiz-app/quizzes/code/ryokotmng_q1.go diff --git a/quiz-app-normal/quizzes/code/sat0ken_q3_question.go b/draft/quiz-app/quizzes/code/sat0ken_q3_question.go similarity index 100% rename from quiz-app-normal/quizzes/code/sat0ken_q3_question.go rename to draft/quiz-app/quizzes/code/sat0ken_q3_question.go diff --git a/quiz-app-normal/quizzes/code/sivchari_q1_answer.go b/draft/quiz-app/quizzes/code/sivchari_q1_answer.go similarity index 100% rename from quiz-app-normal/quizzes/code/sivchari_q1_answer.go rename to draft/quiz-app/quizzes/code/sivchari_q1_answer.go diff --git a/quiz-app-normal/quizzes/code/sivchari_q1_question.go b/draft/quiz-app/quizzes/code/sivchari_q1_question.go similarity index 100% rename from quiz-app-normal/quizzes/code/sivchari_q1_question.go rename to draft/quiz-app/quizzes/code/sivchari_q1_question.go diff --git a/quiz-app-normal/quizzes/code/sivchari_q2_answer.go b/draft/quiz-app/quizzes/code/sivchari_q2_answer.go similarity index 100% rename from quiz-app-normal/quizzes/code/sivchari_q2_answer.go rename to draft/quiz-app/quizzes/code/sivchari_q2_answer.go diff --git a/quiz-app-normal/quizzes/code/sivchari_q2_question.go b/draft/quiz-app/quizzes/code/sivchari_q2_question.go similarity index 100% rename from quiz-app-normal/quizzes/code/sivchari_q2_question.go rename to draft/quiz-app/quizzes/code/sivchari_q2_question.go diff --git a/quiz-app-normal/quizzes/code/tesso57_q1_answer.go b/draft/quiz-app/quizzes/code/tesso57_q1_answer.go similarity index 100% rename from quiz-app-normal/quizzes/code/tesso57_q1_answer.go rename to draft/quiz-app/quizzes/code/tesso57_q1_answer.go diff --git a/quiz-app-normal/quizzes/code/tesso57_q1_question.go b/draft/quiz-app/quizzes/code/tesso57_q1_question.go similarity index 100% rename from quiz-app-normal/quizzes/code/tesso57_q1_question.go rename to draft/quiz-app/quizzes/code/tesso57_q1_question.go diff --git a/quiz-app-normal/quizzes/code/tesso57_q2_answer.go b/draft/quiz-app/quizzes/code/tesso57_q2_answer.go similarity index 100% rename from quiz-app-normal/quizzes/code/tesso57_q2_answer.go rename to draft/quiz-app/quizzes/code/tesso57_q2_answer.go diff --git a/quiz-app-normal/quizzes/code/tesso57_q2_question.go b/draft/quiz-app/quizzes/code/tesso57_q2_question.go similarity index 100% rename from quiz-app-normal/quizzes/code/tesso57_q2_question.go rename to draft/quiz-app/quizzes/code/tesso57_q2_question.go diff --git a/quiz-app-normal/quizzes/code/tesso57_q3_answer.go b/draft/quiz-app/quizzes/code/tesso57_q3_answer.go similarity index 100% rename from quiz-app-normal/quizzes/code/tesso57_q3_answer.go rename to draft/quiz-app/quizzes/code/tesso57_q3_answer.go diff --git a/quiz-app-normal/quizzes/code/tesso57_q3_question.go b/draft/quiz-app/quizzes/code/tesso57_q3_question.go similarity index 100% rename from quiz-app-normal/quizzes/code/tesso57_q3_question.go rename to draft/quiz-app/quizzes/code/tesso57_q3_question.go diff --git a/quiz-app-normal/quizzes/code/tesso57_q4_answer.go b/draft/quiz-app/quizzes/code/tesso57_q4_answer.go similarity index 100% rename from quiz-app-normal/quizzes/code/tesso57_q4_answer.go rename to draft/quiz-app/quizzes/code/tesso57_q4_answer.go diff --git a/quiz-app-normal/quizzes/code/tesso57_q5_answer.go b/draft/quiz-app/quizzes/code/tesso57_q5_answer.go similarity index 100% rename from quiz-app-normal/quizzes/code/tesso57_q5_answer.go rename to draft/quiz-app/quizzes/code/tesso57_q5_answer.go diff --git a/quiz-app-normal/quizzes/code/tesso57_q5_question.go b/draft/quiz-app/quizzes/code/tesso57_q5_question.go similarity index 100% rename from quiz-app-normal/quizzes/code/tesso57_q5_question.go rename to draft/quiz-app/quizzes/code/tesso57_q5_question.go diff --git a/quiz-app-normal/quizzes/code/tomtwinkle_q10_question.go b/draft/quiz-app/quizzes/code/tomtwinkle_q10_question.go similarity index 100% rename from quiz-app-normal/quizzes/code/tomtwinkle_q10_question.go rename to draft/quiz-app/quizzes/code/tomtwinkle_q10_question.go diff --git a/quiz-app-normal/quizzes/code/tomtwinkle_q1_answer.go b/draft/quiz-app/quizzes/code/tomtwinkle_q1_answer.go similarity index 100% rename from quiz-app-normal/quizzes/code/tomtwinkle_q1_answer.go rename to draft/quiz-app/quizzes/code/tomtwinkle_q1_answer.go diff --git a/quiz-app-normal/quizzes/code/tomtwinkle_q2_answer.go b/draft/quiz-app/quizzes/code/tomtwinkle_q2_answer.go similarity index 100% rename from quiz-app-normal/quizzes/code/tomtwinkle_q2_answer.go rename to draft/quiz-app/quizzes/code/tomtwinkle_q2_answer.go diff --git a/quiz-app-normal/quizzes/code/tomtwinkle_q2_question.go b/draft/quiz-app/quizzes/code/tomtwinkle_q2_question.go similarity index 100% rename from quiz-app-normal/quizzes/code/tomtwinkle_q2_question.go rename to draft/quiz-app/quizzes/code/tomtwinkle_q2_question.go diff --git a/quiz-app-normal/quizzes/code/tomtwinkle_q4_answer.go b/draft/quiz-app/quizzes/code/tomtwinkle_q4_answer.go similarity index 100% rename from quiz-app-normal/quizzes/code/tomtwinkle_q4_answer.go rename to draft/quiz-app/quizzes/code/tomtwinkle_q4_answer.go diff --git a/quiz-app-normal/quizzes/code/tomtwinkle_q4_question.go b/draft/quiz-app/quizzes/code/tomtwinkle_q4_question.go similarity index 100% rename from quiz-app-normal/quizzes/code/tomtwinkle_q4_question.go rename to draft/quiz-app/quizzes/code/tomtwinkle_q4_question.go diff --git a/quiz-app-normal/quizzes/code/tomtwinkle_q5_answer.go b/draft/quiz-app/quizzes/code/tomtwinkle_q5_answer.go similarity index 100% rename from quiz-app-normal/quizzes/code/tomtwinkle_q5_answer.go rename to draft/quiz-app/quizzes/code/tomtwinkle_q5_answer.go diff --git a/quiz-app-normal/quizzes/code/tomtwinkle_q5_question.go b/draft/quiz-app/quizzes/code/tomtwinkle_q5_question.go similarity index 100% rename from quiz-app-normal/quizzes/code/tomtwinkle_q5_question.go rename to draft/quiz-app/quizzes/code/tomtwinkle_q5_question.go diff --git a/quiz-app-normal/quizzes/code/tomtwinkle_q6_answer.go b/draft/quiz-app/quizzes/code/tomtwinkle_q6_answer.go similarity index 100% rename from quiz-app-normal/quizzes/code/tomtwinkle_q6_answer.go rename to draft/quiz-app/quizzes/code/tomtwinkle_q6_answer.go diff --git a/quiz-app-normal/quizzes/code/tomtwinkle_q6_question.go b/draft/quiz-app/quizzes/code/tomtwinkle_q6_question.go similarity index 100% rename from quiz-app-normal/quizzes/code/tomtwinkle_q6_question.go rename to draft/quiz-app/quizzes/code/tomtwinkle_q6_question.go diff --git a/quiz-app-normal/quizzes/code/tomtwinkle_q7_answer.go b/draft/quiz-app/quizzes/code/tomtwinkle_q7_answer.go similarity index 100% rename from quiz-app-normal/quizzes/code/tomtwinkle_q7_answer.go rename to draft/quiz-app/quizzes/code/tomtwinkle_q7_answer.go diff --git a/quiz-app-normal/quizzes/code/tomtwinkle_q7_question.go b/draft/quiz-app/quizzes/code/tomtwinkle_q7_question.go similarity index 100% rename from quiz-app-normal/quizzes/code/tomtwinkle_q7_question.go rename to draft/quiz-app/quizzes/code/tomtwinkle_q7_question.go diff --git a/quiz-app-normal/quizzes/code/tomtwinkle_q8_answer.go b/draft/quiz-app/quizzes/code/tomtwinkle_q8_answer.go similarity index 100% rename from quiz-app-normal/quizzes/code/tomtwinkle_q8_answer.go rename to draft/quiz-app/quizzes/code/tomtwinkle_q8_answer.go diff --git a/quiz-app-normal/quizzes/code/tomtwinkle_q8_question.go b/draft/quiz-app/quizzes/code/tomtwinkle_q8_question.go similarity index 100% rename from quiz-app-normal/quizzes/code/tomtwinkle_q8_question.go rename to draft/quiz-app/quizzes/code/tomtwinkle_q8_question.go diff --git a/quiz-app-normal/quizzes/code/tomtwinkle_q9_answer.go b/draft/quiz-app/quizzes/code/tomtwinkle_q9_answer.go similarity index 100% rename from quiz-app-normal/quizzes/code/tomtwinkle_q9_answer.go rename to draft/quiz-app/quizzes/code/tomtwinkle_q9_answer.go diff --git a/quiz-app-normal/quizzes/code/wancom_q1_answer.go b/draft/quiz-app/quizzes/code/wancom_q1_answer.go similarity index 100% rename from quiz-app-normal/quizzes/code/wancom_q1_answer.go rename to draft/quiz-app/quizzes/code/wancom_q1_answer.go diff --git a/quiz-app-normal/quizzes/code/wancom_q1_question.go b/draft/quiz-app/quizzes/code/wancom_q1_question.go similarity index 100% rename from quiz-app-normal/quizzes/code/wancom_q1_question.go rename to draft/quiz-app/quizzes/code/wancom_q1_question.go diff --git a/quiz-app-normal/quizzes/code/wancom_q3_answer.go b/draft/quiz-app/quizzes/code/wancom_q3_answer.go similarity index 100% rename from quiz-app-normal/quizzes/code/wancom_q3_answer.go rename to draft/quiz-app/quizzes/code/wancom_q3_answer.go diff --git a/quiz-app-normal/quizzes/quizes.yaml b/draft/quiz-app/quizzes/quizes.yaml similarity index 100% rename from quiz-app-normal/quizzes/quizes.yaml rename to draft/quiz-app/quizzes/quizes.yaml diff --git a/quiz-app/test/browser-ui.test.js b/draft/quiz-app/test/browser-ui.test.js similarity index 100% rename from quiz-app/test/browser-ui.test.js rename to draft/quiz-app/test/browser-ui.test.js diff --git a/quizzes/Rtosshy/code/q1_answer.go b/draft/quizzes/Rtosshy/code/q1_answer.go similarity index 100% rename from quizzes/Rtosshy/code/q1_answer.go rename to draft/quizzes/Rtosshy/code/q1_answer.go diff --git a/quizzes/Rtosshy/code/q1_question.go b/draft/quizzes/Rtosshy/code/q1_question.go similarity index 100% rename from quizzes/Rtosshy/code/q1_question.go rename to draft/quizzes/Rtosshy/code/q1_question.go diff --git a/quizzes/Rtosshy/code/q3_question.go b/draft/quizzes/Rtosshy/code/q3_question.go similarity index 100% rename from quizzes/Rtosshy/code/q3_question.go rename to draft/quizzes/Rtosshy/code/q3_question.go diff --git a/quizzes/Rtosshy/quiz.yaml b/draft/quizzes/Rtosshy/quiz.yaml similarity index 100% rename from quizzes/Rtosshy/quiz.yaml rename to draft/quizzes/Rtosshy/quiz.yaml diff --git a/quizzes/_template/code/q1.go b/draft/quizzes/_template/code/q1.go similarity index 100% rename from quizzes/_template/code/q1.go rename to draft/quizzes/_template/code/q1.go diff --git a/quizzes/_template/code/q2_answer.go b/draft/quizzes/_template/code/q2_answer.go similarity index 100% rename from quizzes/_template/code/q2_answer.go rename to draft/quizzes/_template/code/q2_answer.go diff --git a/quizzes/_template/code/q2_question.go b/draft/quizzes/_template/code/q2_question.go similarity index 100% rename from quizzes/_template/code/q2_question.go rename to draft/quizzes/_template/code/q2_question.go diff --git a/quizzes/_template/quiz.yaml b/draft/quizzes/_template/quiz.yaml similarity index 100% rename from quizzes/_template/quiz.yaml rename to draft/quizzes/_template/quiz.yaml diff --git a/quizzes/hasegawa_itsuki/code/q3_question.go b/draft/quizzes/hasegawa_itsuki/code/q3_question.go similarity index 100% rename from quizzes/hasegawa_itsuki/code/q3_question.go rename to draft/quizzes/hasegawa_itsuki/code/q3_question.go diff --git a/quizzes/hasegawa_itsuki/quiz.yaml b/draft/quizzes/hasegawa_itsuki/quiz.yaml similarity index 100% rename from quizzes/hasegawa_itsuki/quiz.yaml rename to draft/quizzes/hasegawa_itsuki/quiz.yaml diff --git a/quizzes/makki_d/code/q2_answer.go b/draft/quizzes/makki_d/code/q2_answer.go similarity index 100% rename from quizzes/makki_d/code/q2_answer.go rename to draft/quizzes/makki_d/code/q2_answer.go diff --git a/quizzes/makki_d/code/q2_question.go b/draft/quizzes/makki_d/code/q2_question.go similarity index 100% rename from quizzes/makki_d/code/q2_question.go rename to draft/quizzes/makki_d/code/q2_question.go diff --git a/quizzes/makki_d/code/q3_question.go b/draft/quizzes/makki_d/code/q3_question.go similarity index 100% rename from quizzes/makki_d/code/q3_question.go rename to draft/quizzes/makki_d/code/q3_question.go diff --git a/quizzes/makki_d/quiz.yaml b/draft/quizzes/makki_d/quiz.yaml similarity index 100% rename from quizzes/makki_d/quiz.yaml rename to draft/quizzes/makki_d/quiz.yaml diff --git a/quizzes/ryokotmng/code/q1_slice.go b/draft/quizzes/ryokotmng/code/q1_slice.go similarity index 100% rename from quizzes/ryokotmng/code/q1_slice.go rename to draft/quizzes/ryokotmng/code/q1_slice.go diff --git a/quizzes/ryokotmng/code/q2_answer.go b/draft/quizzes/ryokotmng/code/q2_answer.go similarity index 100% rename from quizzes/ryokotmng/code/q2_answer.go rename to draft/quizzes/ryokotmng/code/q2_answer.go diff --git a/quizzes/ryokotmng/code/q2_defer.go b/draft/quizzes/ryokotmng/code/q2_defer.go similarity index 100% rename from quizzes/ryokotmng/code/q2_defer.go rename to draft/quizzes/ryokotmng/code/q2_defer.go diff --git a/quizzes/ryokotmng/code/q2_question.go b/draft/quizzes/ryokotmng/code/q2_question.go similarity index 100% rename from quizzes/ryokotmng/code/q2_question.go rename to draft/quizzes/ryokotmng/code/q2_question.go diff --git a/quizzes/ryokotmng/quiz.yaml b/draft/quizzes/ryokotmng/quiz.yaml similarity index 100% rename from quizzes/ryokotmng/quiz.yaml rename to draft/quizzes/ryokotmng/quiz.yaml diff --git a/quizzes/sat0ken/code/q3_question.go b/draft/quizzes/sat0ken/code/q3_question.go similarity index 100% rename from quizzes/sat0ken/code/q3_question.go rename to draft/quizzes/sat0ken/code/q3_question.go diff --git a/quizzes/sat0ken/code/q4_question.go b/draft/quizzes/sat0ken/code/q4_question.go similarity index 100% rename from quizzes/sat0ken/code/q4_question.go rename to draft/quizzes/sat0ken/code/q4_question.go diff --git a/quizzes/sat0ken/quiz.yaml b/draft/quizzes/sat0ken/quiz.yaml similarity index 100% rename from quizzes/sat0ken/quiz.yaml rename to draft/quizzes/sat0ken/quiz.yaml diff --git a/quizzes/sivchari/code/q1_answer.go b/draft/quizzes/sivchari/code/q1_answer.go similarity index 100% rename from quizzes/sivchari/code/q1_answer.go rename to draft/quizzes/sivchari/code/q1_answer.go diff --git a/quizzes/sivchari/code/q1_question.go b/draft/quizzes/sivchari/code/q1_question.go similarity index 100% rename from quizzes/sivchari/code/q1_question.go rename to draft/quizzes/sivchari/code/q1_question.go diff --git a/quizzes/sivchari/code/q2_answer.go b/draft/quizzes/sivchari/code/q2_answer.go similarity index 100% rename from quizzes/sivchari/code/q2_answer.go rename to draft/quizzes/sivchari/code/q2_answer.go diff --git a/quizzes/sivchari/code/q2_question.go b/draft/quizzes/sivchari/code/q2_question.go similarity index 100% rename from quizzes/sivchari/code/q2_question.go rename to draft/quizzes/sivchari/code/q2_question.go diff --git a/quizzes/sivchari/quiz.yaml b/draft/quizzes/sivchari/quiz.yaml similarity index 100% rename from quizzes/sivchari/quiz.yaml rename to draft/quizzes/sivchari/quiz.yaml diff --git a/quizzes/tesso57/code/q1_answer.go b/draft/quizzes/tesso57/code/q1_answer.go similarity index 100% rename from quizzes/tesso57/code/q1_answer.go rename to draft/quizzes/tesso57/code/q1_answer.go diff --git a/quizzes/tesso57/code/q1_question.go b/draft/quizzes/tesso57/code/q1_question.go similarity index 100% rename from quizzes/tesso57/code/q1_question.go rename to draft/quizzes/tesso57/code/q1_question.go diff --git a/quizzes/tesso57/code/q2_answer.go b/draft/quizzes/tesso57/code/q2_answer.go similarity index 100% rename from quizzes/tesso57/code/q2_answer.go rename to draft/quizzes/tesso57/code/q2_answer.go diff --git a/quizzes/tesso57/code/q2_question.go b/draft/quizzes/tesso57/code/q2_question.go similarity index 100% rename from quizzes/tesso57/code/q2_question.go rename to draft/quizzes/tesso57/code/q2_question.go diff --git a/quizzes/tesso57/code/q3_answer.go b/draft/quizzes/tesso57/code/q3_answer.go similarity index 100% rename from quizzes/tesso57/code/q3_answer.go rename to draft/quizzes/tesso57/code/q3_answer.go diff --git a/quizzes/tesso57/code/q3_question.go b/draft/quizzes/tesso57/code/q3_question.go similarity index 100% rename from quizzes/tesso57/code/q3_question.go rename to draft/quizzes/tesso57/code/q3_question.go diff --git a/quizzes/tesso57/code/q4_answer.go b/draft/quizzes/tesso57/code/q4_answer.go similarity index 100% rename from quizzes/tesso57/code/q4_answer.go rename to draft/quizzes/tesso57/code/q4_answer.go diff --git a/quizzes/tesso57/code/q5_answer.go b/draft/quizzes/tesso57/code/q5_answer.go similarity index 100% rename from quizzes/tesso57/code/q5_answer.go rename to draft/quizzes/tesso57/code/q5_answer.go diff --git a/quizzes/tesso57/code/q5_question.go b/draft/quizzes/tesso57/code/q5_question.go similarity index 100% rename from quizzes/tesso57/code/q5_question.go rename to draft/quizzes/tesso57/code/q5_question.go diff --git a/quizzes/tesso57/quiz.yaml b/draft/quizzes/tesso57/quiz.yaml similarity index 100% rename from quizzes/tesso57/quiz.yaml rename to draft/quizzes/tesso57/quiz.yaml diff --git a/quizzes/tomtwinkle/code/q10_question.go b/draft/quizzes/tomtwinkle/code/q10_question.go similarity index 100% rename from quizzes/tomtwinkle/code/q10_question.go rename to draft/quizzes/tomtwinkle/code/q10_question.go diff --git a/quizzes/tomtwinkle/code/q1_answer.go b/draft/quizzes/tomtwinkle/code/q1_answer.go similarity index 100% rename from quizzes/tomtwinkle/code/q1_answer.go rename to draft/quizzes/tomtwinkle/code/q1_answer.go diff --git a/quizzes/tomtwinkle/code/q2_answer.go b/draft/quizzes/tomtwinkle/code/q2_answer.go similarity index 100% rename from quizzes/tomtwinkle/code/q2_answer.go rename to draft/quizzes/tomtwinkle/code/q2_answer.go diff --git a/quizzes/tomtwinkle/code/q2_question.go b/draft/quizzes/tomtwinkle/code/q2_question.go similarity index 100% rename from quizzes/tomtwinkle/code/q2_question.go rename to draft/quizzes/tomtwinkle/code/q2_question.go diff --git a/quizzes/tomtwinkle/code/q3_answer.go b/draft/quizzes/tomtwinkle/code/q3_answer.go similarity index 100% rename from quizzes/tomtwinkle/code/q3_answer.go rename to draft/quizzes/tomtwinkle/code/q3_answer.go diff --git a/quizzes/tomtwinkle/code/q3_question.go b/draft/quizzes/tomtwinkle/code/q3_question.go similarity index 100% rename from quizzes/tomtwinkle/code/q3_question.go rename to draft/quizzes/tomtwinkle/code/q3_question.go diff --git a/quizzes/tomtwinkle/code/q4_answer.go b/draft/quizzes/tomtwinkle/code/q4_answer.go similarity index 100% rename from quizzes/tomtwinkle/code/q4_answer.go rename to draft/quizzes/tomtwinkle/code/q4_answer.go diff --git a/quizzes/tomtwinkle/code/q4_question.go b/draft/quizzes/tomtwinkle/code/q4_question.go similarity index 100% rename from quizzes/tomtwinkle/code/q4_question.go rename to draft/quizzes/tomtwinkle/code/q4_question.go diff --git a/quizzes/tomtwinkle/code/q5_answer.go b/draft/quizzes/tomtwinkle/code/q5_answer.go similarity index 100% rename from quizzes/tomtwinkle/code/q5_answer.go rename to draft/quizzes/tomtwinkle/code/q5_answer.go diff --git a/quizzes/tomtwinkle/code/q5_question.go b/draft/quizzes/tomtwinkle/code/q5_question.go similarity index 100% rename from quizzes/tomtwinkle/code/q5_question.go rename to draft/quizzes/tomtwinkle/code/q5_question.go diff --git a/quizzes/tomtwinkle/code/q6_answer.go b/draft/quizzes/tomtwinkle/code/q6_answer.go similarity index 100% rename from quizzes/tomtwinkle/code/q6_answer.go rename to draft/quizzes/tomtwinkle/code/q6_answer.go diff --git a/quizzes/tomtwinkle/code/q6_question.go b/draft/quizzes/tomtwinkle/code/q6_question.go similarity index 100% rename from quizzes/tomtwinkle/code/q6_question.go rename to draft/quizzes/tomtwinkle/code/q6_question.go diff --git a/quizzes/tomtwinkle/code/q7_answer.go b/draft/quizzes/tomtwinkle/code/q7_answer.go similarity index 100% rename from quizzes/tomtwinkle/code/q7_answer.go rename to draft/quizzes/tomtwinkle/code/q7_answer.go diff --git a/quizzes/tomtwinkle/code/q7_question.go b/draft/quizzes/tomtwinkle/code/q7_question.go similarity index 100% rename from quizzes/tomtwinkle/code/q7_question.go rename to draft/quizzes/tomtwinkle/code/q7_question.go diff --git a/quizzes/tomtwinkle/code/q8_answer.go b/draft/quizzes/tomtwinkle/code/q8_answer.go similarity index 100% rename from quizzes/tomtwinkle/code/q8_answer.go rename to draft/quizzes/tomtwinkle/code/q8_answer.go diff --git a/quizzes/tomtwinkle/code/q8_question.go b/draft/quizzes/tomtwinkle/code/q8_question.go similarity index 100% rename from quizzes/tomtwinkle/code/q8_question.go rename to draft/quizzes/tomtwinkle/code/q8_question.go diff --git a/quizzes/tomtwinkle/code/q9_answer.go b/draft/quizzes/tomtwinkle/code/q9_answer.go similarity index 100% rename from quizzes/tomtwinkle/code/q9_answer.go rename to draft/quizzes/tomtwinkle/code/q9_answer.go diff --git a/quizzes/tomtwinkle/quiz.yaml b/draft/quizzes/tomtwinkle/quiz.yaml similarity index 100% rename from quizzes/tomtwinkle/quiz.yaml rename to draft/quizzes/tomtwinkle/quiz.yaml diff --git a/quizzes/wancom/code/q1_answer.go b/draft/quizzes/wancom/code/q1_answer.go similarity index 100% rename from quizzes/wancom/code/q1_answer.go rename to draft/quizzes/wancom/code/q1_answer.go diff --git a/quizzes/wancom/code/q1_question.go b/draft/quizzes/wancom/code/q1_question.go similarity index 100% rename from quizzes/wancom/code/q1_question.go rename to draft/quizzes/wancom/code/q1_question.go diff --git a/quizzes/wancom/code/q3_answer.go b/draft/quizzes/wancom/code/q3_answer.go similarity index 100% rename from quizzes/wancom/code/q3_answer.go rename to draft/quizzes/wancom/code/q3_answer.go diff --git a/quizzes/wancom/quiz.yaml b/draft/quizzes/wancom/quiz.yaml similarity index 100% rename from quizzes/wancom/quiz.yaml rename to draft/quizzes/wancom/quiz.yaml diff --git a/scripts/setup.sh b/draft/scripts/setup.sh similarity index 100% rename from scripts/setup.sh rename to draft/scripts/setup.sh diff --git a/scripts/sync_answer_code_play_refs/main.go b/draft/scripts/sync_answer_code_play_refs/main.go similarity index 100% rename from scripts/sync_answer_code_play_refs/main.go rename to draft/scripts/sync_answer_code_play_refs/main.go diff --git a/scripts/sync_answer_code_play_refs/main_test.go b/draft/scripts/sync_answer_code_play_refs/main_test.go similarity index 100% rename from scripts/sync_answer_code_play_refs/main_test.go rename to draft/scripts/sync_answer_code_play_refs/main_test.go diff --git a/quiz-app-normal/cmd/generatequizdata/main.go b/quiz-app-normal/cmd/generatequizdata/main.go deleted file mode 100644 index 96e441a..0000000 --- a/quiz-app-normal/cmd/generatequizdata/main.go +++ /dev/null @@ -1,43 +0,0 @@ -package main - -import ( - "flag" - "log/slog" - "os" - "path/filepath" - - "github.com/GoCon/2026-codelab/quiz-app-normal/internal/quizdata" -) - -func main() { - logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) - basePath := flag.String("base", "quizzes", "quiz data base directory") - outPath := flag.String("out", "", "output path") - flag.Parse() - - if *outPath == "" { - logger.Error("-out is required") - os.Exit(1) - } - - quizzes, codeFiles, err := quizdata.LoadFromBase(*basePath) - if err != nil { - logger.Error("failed to load quiz data", "error", err) - os.Exit(1) - } - staticQuizzes := quizdata.BuildStaticQuizzes(quizzes, codeFiles) - js, err := quizdata.MarshalJavaScript(staticQuizzes) - if err != nil { - logger.Error("failed to marshal quiz data", "error", err) - os.Exit(1) - } - - if err := os.MkdirAll(filepath.Dir(*outPath), 0o755); err != nil { - logger.Error("failed to create output directory", "error", err, "path", filepath.Dir(*outPath)) - os.Exit(1) - } - if err := os.WriteFile(*outPath, js, 0o644); err != nil { - logger.Error("failed to write quiz data", "error", err, "path", *outPath) - os.Exit(1) - } -} diff --git a/quiz-app-normal/cmd/local/main.go b/quiz-app-normal/cmd/local/main.go deleted file mode 100644 index a63e7bf..0000000 --- a/quiz-app-normal/cmd/local/main.go +++ /dev/null @@ -1,48 +0,0 @@ -// local はローカル開発用の HTTP サーバー。 -// quiz-data.js を起動時に生成して public/ と一緒に配信する。 -// 使用方法: make local (quiz-app-normal/ ディレクトリから実行) -package main - -import ( - "log/slog" - "net/http" - "os" - - "github.com/GoCon/2026-codelab/quiz-app-normal/internal/quizdata" -) - -func fail(logger *slog.Logger, msg string, err error) { - logger.Error(msg, "error", err) - os.Exit(1) -} - -func main() { - const quizDataBase = "quizzes" - logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) - - quizzes, codeFiles, err := quizdata.LoadFromBase(quizDataBase) - if err != nil { - fail(logger, "failed to load quiz data", err) - } - staticQuizzes := quizdata.BuildStaticQuizzes(quizzes, codeFiles) - quizDataJS, err := quizdata.MarshalJavaScript(staticQuizzes) - if err != nil { - fail(logger, "failed to marshal quiz data", err) - } - - mux := http.NewServeMux() - mux.HandleFunc("GET /quiz-data.js", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/javascript; charset=utf-8") - w.Write(quizDataJS) - }) - mux.Handle("/", http.FileServer(http.Dir("public"))) - - addr := ":8789" - if p := os.Getenv("PORT"); p != "" { - addr = ":" + p - } - logger.Info("starting local server", "url", "http://localhost"+addr) - if err := http.ListenAndServe(addr, mux); err != nil { - fail(logger, "local server exited with error", err) - } -} diff --git a/quiz-app-normal/go.mod b/quiz-app-normal/go.mod deleted file mode 100644 index 0c860c2..0000000 --- a/quiz-app-normal/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module github.com/GoCon/2026-codelab/quiz-app-normal - -go 1.25.9 - -require gopkg.in/yaml.v3 v3.0.1 diff --git a/quiz-app-normal/internal/quizdata/quizdata.go b/quiz-app-normal/internal/quizdata/quizdata.go deleted file mode 100644 index 58c2d29..0000000 --- a/quiz-app-normal/internal/quizdata/quizdata.go +++ /dev/null @@ -1,128 +0,0 @@ -package quizdata - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/GoCon/2026-codelab/quiz-app-normal/internal/quizhandler" -) - -// StaticQuiz is the full quiz payload embedded into the static GitHub Pages build. -type StaticQuiz struct { - ID string `json:"id"` - Title string `json:"title"` - TitleEn string `json:"title_en,omitempty"` - Text string `json:"text"` - TextEn string `json:"text_en,omitempty"` - Mode string `json:"mode,omitempty"` - Choices []string `json:"choices"` - ChoicesEn []string `json:"choices_en,omitempty"` - Answer int `json:"answer"` - Explanation string `json:"explanation"` - ExplanationEn string `json:"explanation_en,omitempty"` - QuestionCode string `json:"question_code,omitempty"` - AnswerCode string `json:"answer_code,omitempty"` - AnswerCodePlayRef string `json:"answer_code_play_ref,omitempty"` -} - -// LoadFromBase reads quizzes and any referenced code files from the given base directory. -func LoadFromBase(basePath string) ([]quizhandler.Quiz, map[string]string, error) { - quizesYAML, err := os.ReadFile(filepath.Join(basePath, "quizes.yaml")) - if err != nil { - return nil, nil, fmt.Errorf("quizes.yaml の読み込みに失敗しました: %w", err) - } - quizzes, err := quizhandler.ParseQuizzes(quizesYAML) - if err != nil { - return nil, nil, fmt.Errorf("quizes.yaml のパースに失敗しました: %w", err) - } - quizzes = FilterNormalModeQuizzes(quizzes) - codeFiles, err := LoadCodeFiles(quizzes, basePath) - if err != nil { - return nil, nil, err - } - return quizzes, codeFiles, nil -} - -// FilterNormalModeQuizzes keeps quizzes that are available to the normal-mode app. -func FilterNormalModeQuizzes(quizzes []quizhandler.Quiz) []quizhandler.Quiz { - filtered := make([]quizhandler.Quiz, 0, len(quizzes)) - for _, quiz := range quizzes { - if quiz.Mode == quizhandler.QuizModeExtra { - continue - } - filtered = append(filtered, quiz) - } - return filtered -} - -// LoadCodeFiles loads every referenced question/answer code file from the base directory. -func LoadCodeFiles(quizzes []quizhandler.Quiz, basePath string) (map[string]string, error) { - files := make(map[string]string) - for _, q := range quizzes { - for _, ref := range []string{q.QuestionCodeRef, q.AnswerCodeRef} { - if ref == "" || files[ref] != "" { - continue - } - data, err := os.ReadFile(filepath.Join(basePath, ref)) - if err != nil { - return nil, fmt.Errorf("コードファイルの読み込みに失敗しました %s: %w", ref, err) - } - files[ref] = StripBuildIgnore(string(data)) - } - } - return files, nil -} - -// BuildStaticQuizzes expands quiz metadata and referenced code into a static payload. -func BuildStaticQuizzes(quizzes []quizhandler.Quiz, codeFiles map[string]string) []StaticQuiz { - staticQuizzes := make([]StaticQuiz, 0, len(quizzes)) - for _, q := range quizzes { - mode := q.Mode - if mode == quizhandler.QuizModeBoth { - mode = "" - } - staticQuiz := StaticQuiz{ - ID: q.ID, - Title: q.Title, - TitleEn: q.TitleEn, - Text: q.Text, - TextEn: q.TextEn, - Mode: mode, - Choices: append([]string(nil), q.Choices...), - ChoicesEn: append([]string(nil), q.ChoicesEn...), - Answer: q.Answer, - Explanation: q.Explanation, - ExplanationEn: q.ExplanationEn, - AnswerCodePlayRef: q.AnswerCodePlayRef, - } - if q.QuestionCodeRef != "" { - staticQuiz.QuestionCode = codeFiles[q.QuestionCodeRef] - } - if q.AnswerCodeRef != "" { - staticQuiz.AnswerCode = codeFiles[q.AnswerCodeRef] - } - staticQuizzes = append(staticQuizzes, staticQuiz) - } - return staticQuizzes -} - -// MarshalJavaScript serializes quiz data as a browser-ready JavaScript asset. -func MarshalJavaScript(quizzes []StaticQuiz) ([]byte, error) { - data, err := json.Marshal(quizzes) - if err != nil { - return nil, fmt.Errorf("クイズデータのシリアライズに失敗しました: %w", err) - } - return []byte("window.__QUIZ_DATA__ = " + string(data) + ";\n"), nil -} - -// StripBuildIgnore removes the leading //go:build ignore directive from code snippets. -func StripBuildIgnore(s string) string { - const directive = "//go:build ignore" - if !strings.HasPrefix(s, directive) { - return s - } - return strings.TrimLeft(strings.TrimPrefix(s, directive), "\r\n") -} diff --git a/quiz-app-normal/internal/quizdata/quizdata_test.go b/quiz-app-normal/internal/quizdata/quizdata_test.go deleted file mode 100644 index d29ce46..0000000 --- a/quiz-app-normal/internal/quizdata/quizdata_test.go +++ /dev/null @@ -1,124 +0,0 @@ -package quizdata_test - -import ( - "os" - "path/filepath" - "strings" - "testing" - - "github.com/GoCon/2026-codelab/quiz-app-normal/internal/quizdata" - "github.com/GoCon/2026-codelab/quiz-app-normal/internal/quizhandler" -) - -func TestStripBuildIgnore(t *testing.T) { - got := quizdata.StripBuildIgnore("//go:build ignore\n\npackage main\n") - if got != "package main\n" { - t.Fatalf("got %q", got) - } -} - -func TestBuildStaticQuizzes(t *testing.T) { - quizzes := []quizhandler.Quiz{{ - ID: "q1", - Title: "title", - TitleEn: "title en", - Text: "text", - TextEn: "text en", - Mode: quizhandler.QuizModeExtra, - Choices: []string{"a", "b"}, - ChoicesEn: []string{"a en", "b en"}, - Answer: 1, - Explanation: "exp", - ExplanationEn: "exp en", - QuestionCodeRef: "code/q1_question.go", - AnswerCodeRef: "code/q1_answer.go", - AnswerCodePlayRef: "https://go.dev/play/p/example", - }} - codeFiles := map[string]string{ - "code/q1_question.go": "package main\n// question", - "code/q1_answer.go": "package main\n// answer", - } - - got := quizdata.BuildStaticQuizzes(quizzes, codeFiles) - if len(got) != 1 { - t.Fatalf("len = %d, want 1", len(got)) - } - if got[0].QuestionCode != codeFiles["code/q1_question.go"] { - t.Fatalf("question code mismatch") - } - if got[0].AnswerCode != codeFiles["code/q1_answer.go"] { - t.Fatalf("answer code mismatch") - } - if got[0].Mode != quizhandler.QuizModeExtra { - t.Fatalf("mode = %q, want %q", got[0].Mode, quizhandler.QuizModeExtra) - } - if got[0].TitleEn != "title en" || got[0].TextEn != "text en" || got[0].ExplanationEn != "exp en" { - t.Fatalf("english localized fields should be preserved: %+v", got[0]) - } - if strings.Join(got[0].ChoicesEn, ",") != "a en,b en" { - t.Fatalf("english choices mismatch: %#v", got[0].ChoicesEn) - } - if got[0].AnswerCodePlayRef == "" { - t.Fatalf("answer code play ref should be preserved") - } -} - -func TestLoadFromBase(t *testing.T) { - dir := t.TempDir() - if err := os.WriteFile(filepath.Join(dir, "quizes.yaml"), []byte(` -- id: "q1" - title: "Q1" - title_en: "Question 1" - text: "text" - text_en: "text en" - question_code_ref: "code/q1_question.go" - choices: ["A", "B"] - choices_en: ["A en", "B en"] - answer: 1 - explanation: "exp" - explanation_en: "exp en" - answer_code_ref: "code/q1_answer.go" -- id: "q2" - title: "Q2" - text: "text" - mode: "extra" - question_code_ref: "code/missing_question.go" - choices: ["A", "B"] - answer: 0 - explanation: "exp" -`), 0o644); err != nil { - t.Fatal(err) - } - if err := os.MkdirAll(filepath.Join(dir, "code"), 0o755); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(dir, "code", "q1_question.go"), []byte("//go:build ignore\n\npackage main\n"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(dir, "code", "q1_answer.go"), []byte("package main\n"), 0o644); err != nil { - t.Fatal(err) - } - - quizzes, codeFiles, err := quizdata.LoadFromBase(dir) - if err != nil { - t.Fatalf("LoadFromBase: %v", err) - } - if len(quizzes) != 1 { - t.Fatalf("quizzes len = %d, want 1 after filtering extra mode", len(quizzes)) - } - if quizzes[0].Mode != quizhandler.QuizModeBoth { - t.Fatalf("mode = %q, want %q", quizzes[0].Mode, quizhandler.QuizModeBoth) - } - if quizzes[0].TitleEn != "Question 1" || quizzes[0].TextEn != "text en" || quizzes[0].ExplanationEn != "exp en" { - t.Fatalf("localized english fields should be loaded: %+v", quizzes[0]) - } - if strings.Join(quizzes[0].ChoicesEn, ",") != "A en,B en" { - t.Fatalf("english choices mismatch: %#v", quizzes[0].ChoicesEn) - } - if got := codeFiles["code/q1_question.go"]; strings.HasPrefix(got, "//go:build ignore") { - t.Fatalf("question code should strip build tag") - } - if _, ok := codeFiles["code/missing_question.go"]; ok { - t.Fatalf("extra-mode code refs should not be loaded: %#v", codeFiles) - } -} diff --git a/quiz-app-normal/internal/quizhandler/handler_test.go b/quiz-app-normal/internal/quizhandler/handler_test.go deleted file mode 100644 index 2edae9f..0000000 --- a/quiz-app-normal/internal/quizhandler/handler_test.go +++ /dev/null @@ -1,243 +0,0 @@ -package quizhandler_test - -import ( - "os" - "path/filepath" - "strings" - "testing" - - "github.com/GoCon/2026-codelab/quiz-app-normal/internal/quizhandler" -) - -func TestNormalizeQuizMode(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - input string - want string - wantErr bool - }{ - {name: "empty defaults to both", input: "", want: quizhandler.QuizModeBoth}, - {name: "both is preserved", input: "both", want: quizhandler.QuizModeBoth}, - {name: "normal is preserved", input: "normal", want: quizhandler.QuizModeNormal}, - {name: "extra is preserved", input: "extra", want: quizhandler.QuizModeExtra}, - {name: "whitespace is trimmed", input: " EXTRA ", want: quizhandler.QuizModeExtra}, - {name: "unknown value errors", input: "surprise", wantErr: true}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - got, err := quizhandler.NormalizeQuizMode(tt.input) - if tt.wantErr { - if err == nil { - t.Fatal("expected error") - } - return - } - if err != nil { - t.Fatalf("NormalizeQuizMode: %v", err) - } - if got != tt.want { - t.Fatalf("got %q, want %q", got, tt.want) - } - }) - } -} - -func TestParseQuizzes_DefaultModeIsBoth(t *testing.T) { - t.Parallel() - - quizzes, err := quizhandler.ParseQuizzes([]byte(` -- id: "q1" - title: "Q1" - text: "text" - choices: ["A", "B"] - answer: 0 - explanation: "exp" -`)) - if err != nil { - t.Fatalf("ParseQuizzes: %v", err) - } - if len(quizzes) != 1 { - t.Fatalf("len = %d, want 1", len(quizzes)) - } - if quizzes[0].Mode != quizhandler.QuizModeBoth { - t.Fatalf("mode = %q, want %q", quizzes[0].Mode, quizhandler.QuizModeBoth) - } - if quizzes[0].TitleEn != quizzes[0].Title { - t.Fatalf("title_en = %q, want fallback %q", quizzes[0].TitleEn, quizzes[0].Title) - } - if quizzes[0].TextEn != quizzes[0].Text { - t.Fatalf("text_en = %q, want fallback %q", quizzes[0].TextEn, quizzes[0].Text) - } - if quizzes[0].ExplanationEn != quizzes[0].Explanation { - t.Fatalf("explanation_en = %q, want fallback %q", quizzes[0].ExplanationEn, quizzes[0].Explanation) - } - if strings.Join(quizzes[0].ChoicesEn, ",") != strings.Join(quizzes[0].Choices, ",") { - t.Fatalf("choices_en = %#v, want fallback %#v", quizzes[0].ChoicesEn, quizzes[0].Choices) - } -} - -func TestParseQuizzes_InvalidModeReturnsError(t *testing.T) { - t.Parallel() - - _, err := quizhandler.ParseQuizzes([]byte(` -- id: "q1" - title: "Q1" - text: "text" - mode: "surprise" - choices: ["A", "B"] - answer: 0 - explanation: "exp" -`)) - if err == nil { - t.Fatal("expected invalid mode error") - } -} - -func TestParseQuizzes_ChoiceLocalizationLengthMustMatch(t *testing.T) { - t.Parallel() - - _, err := quizhandler.ParseQuizzes([]byte(` -- id: "q1" - title: "Q1" - text: "text" - choices: ["A", "B"] - choices_en: ["A only"] - answer: 0 - explanation: "exp" -`)) - if err == nil { - t.Fatal("expected choices_en length error") - } -} - -func TestParseQuizzes_EnglishFallbacksAndPartialChoices(t *testing.T) { - t.Parallel() - - quizzes, err := quizhandler.ParseQuizzes([]byte(` -- id: "q1" - title: "Q1" - title_en: "Question 1" - text: "text" - choices: ["A", "B"] - choices_en: ["A en", ""] - answer: 0 - explanation: "exp" -`)) - if err != nil { - t.Fatalf("ParseQuizzes: %v", err) - } - if len(quizzes) != 1 { - t.Fatalf("len = %d, want 1", len(quizzes)) - } - if quizzes[0].TitleEn != "Question 1" { - t.Fatalf("title_en = %q, want Question 1", quizzes[0].TitleEn) - } - if quizzes[0].TextEn != "text" { - t.Fatalf("text_en = %q, want fallback text", quizzes[0].TextEn) - } - if quizzes[0].ExplanationEn != "exp" { - t.Fatalf("explanation_en = %q, want fallback exp", quizzes[0].ExplanationEn) - } - if strings.Join(quizzes[0].ChoicesEn, ",") != "A en,B" { - t.Fatalf("choices_en = %#v, want fallback on empty values", quizzes[0].ChoicesEn) - } -} - -func loadRealQuizzes(t *testing.T) []quizhandler.Quiz { - t.Helper() - - data, err := os.ReadFile("../../quizzes/quizes.yaml") - if err != nil { - t.Fatalf("quizes.yaml を読み込めません: %v", err) - } - quizzes, err := quizhandler.ParseQuizzes(data) - if err != nil { - t.Fatalf("quizes.yaml のパースに失敗しました: %v", err) - } - return quizzes -} - -func TestQuizesYAML_UniqueIDs(t *testing.T) { - quizzes := loadRealQuizzes(t) - seen := make(map[string]int) - for _, q := range quizzes { - seen[q.ID]++ - } - for id, count := range seen { - if count > 1 { - t.Errorf("ID %q が %d 回重複しています", id, count) - } - } -} - -func TestQuizesYAML_NoEmptyIDs(t *testing.T) { - for _, q := range loadRealQuizzes(t) { - if q.ID == "" { - t.Errorf("空の ID を持つクイズがあります: %+v", q) - } - } -} - -func TestQuizesYAML_IDsMatchEnglishTitles(t *testing.T) { - for _, q := range loadRealQuizzes(t) { - if q.ID != q.TitleEn { - t.Errorf("quiz title_en=%q: id=%q, want %q", q.TitleEn, q.ID, q.TitleEn) - } - } -} - -func TestQuizesYAML_AnswerIndexInRange(t *testing.T) { - for _, q := range loadRealQuizzes(t) { - if q.Answer < 0 || q.Answer >= len(q.Choices) { - t.Errorf("quiz %q: answer=%d は choices の範囲外 (len=%d)", q.ID, q.Answer, len(q.Choices)) - } - } -} - -func TestQuizesYAML_SufficientChoices(t *testing.T) { - for _, q := range loadRealQuizzes(t) { - if len(q.Choices) < 2 { - t.Errorf("quiz %q: choices が %d 個しかありません(最低 2 個必要)", q.ID, len(q.Choices)) - } - } -} - -func TestQuizesYAML_EnglishChoicesMatchJapaneseCount(t *testing.T) { - for _, q := range loadRealQuizzes(t) { - if len(q.ChoicesEn) != len(q.Choices) { - t.Errorf("quiz %q: choices_en が %d 個で choices の %d 個と一致しません", q.ID, len(q.ChoicesEn), len(q.Choices)) - } - } -} - -func TestQuizesYAML_NoEmptyExplanations(t *testing.T) { - for _, q := range loadRealQuizzes(t) { - if strings.TrimSpace(q.Explanation) == "" { - t.Errorf("quiz %q: explanation が空です", q.ID) - } - } -} - -func TestQuizesYAML_CodeRefsExist(t *testing.T) { - for _, q := range loadRealQuizzes(t) { - for _, ref := range []struct { - name string - path string - }{ - {name: "question_code_ref", path: q.QuestionCodeRef}, - {name: "answer_code_ref", path: q.AnswerCodeRef}, - } { - if ref.path == "" { - continue - } - if _, err := os.Stat(filepath.Join("../../quizzes", ref.path)); err != nil { - t.Errorf("quiz %q: %s=%q の参照先が存在しません: %v", q.ID, ref.name, ref.path, err) - } - } - } -} diff --git a/quiz-app-normal/public/index.html b/quiz-app-normal/public/index.html deleted file mode 100644 index 96b2869..0000000 --- a/quiz-app-normal/public/index.html +++ /dev/null @@ -1,2367 +0,0 @@ - - - - - - Go Conference 2026 CodeLab - - - - - - - - - - - -
- -
-
- -
- - -
-
-
-
-
-
-
- Answered -
- 0 / 5 - 回答済み -
-
-
-
-
-
- -
-
-
- -
-
-
- -
-
WELCOME
-

Go Conference 2026 CodeLab

-

- ランダムに選ばれた 5 問に答える Go クイズです。
- 全問正解で素敵なご褒美が貰えます。 -

- -
- -
- -
- -

このブラウザでは問題一覧モードが解放されています。

-
-
- - - - - - - -
- -
クイズ終了! あなたのスコア
-
-
-

エクストラモード全問正解! スペシャルページが解放されました。

- - スペシャルページへ進む - -
- -
-
解き終わるまでの時間
-
-

全問正解の記録を残せます。ニックネームを入力して送信してください。

-
- - -
-

-
-
- - - - - -
-
-
-
- - - - -
- -
- - - - diff --git a/quiz-app-normal/public/preview/index.html b/quiz-app-normal/public/preview/index.html deleted file mode 100644 index 913b581..0000000 --- a/quiz-app-normal/public/preview/index.html +++ /dev/null @@ -1,1091 +0,0 @@ - - - - - - Go Conference 2026 CodeLab - 問題一覧 - - - - - - - - - - - -
- -
-
- -
- - -
-
-
-
- -
-
- -

クイズ出題一覧

-

一覧から問題を選ぶと、解説・コード付きで内容を閲覧できます。

-
- -
-
- - - - - - - -
問題ID
-
-
-
-
- - - - - - - - - diff --git a/quiz-app-normal/public/quiz-config.js b/quiz-app-normal/public/quiz-config.js deleted file mode 100644 index e1da9e4..0000000 --- a/quiz-app-normal/public/quiz-config.js +++ /dev/null @@ -1,4 +0,0 @@ -window.__QUIZ_APP_CONFIG__ = Object.assign(window.__QUIZ_APP_CONFIG__ || {}, { - telemetryEndpoint: '', - previewUnlockCode: 'gofar,gotogether', -}); diff --git a/quiz-app-normal/test/browser-ui.test.js b/quiz-app-normal/test/browser-ui.test.js deleted file mode 100644 index df59f58..0000000 --- a/quiz-app-normal/test/browser-ui.test.js +++ /dev/null @@ -1,976 +0,0 @@ -const assert = require('node:assert/strict'); -const fs = require('node:fs'); -const path = require('node:path'); -const test = require('node:test'); -const vm = require('node:vm'); - -const appHTML = fs.readFileSync(path.join(__dirname, '..', 'public', 'index.html'), 'utf8'); -const extraModeCSS = fs.readFileSync(path.join(__dirname, '..', 'public', 'assets', 'extra-mode.css'), 'utf8'); -const extraModeScript = fs.readFileSync(path.join(__dirname, '..', 'public', 'assets', 'extra-mode.js'), 'utf8'); -const telemetryGasScript = fs.readFileSync(path.join(__dirname, '..', 'gas', 'telemetry.gs'), 'utf8'); -const previewHTML = fs.readFileSync(path.join(__dirname, '..', 'public', 'preview', 'index.html'), 'utf8'); -const scripts = [...appHTML.matchAll(/