Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/archive-closed-issue.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,10 @@ jobs:
git commit -m "Archive issue #${ISSUE_NUMBER}"
git push origin "HEAD:${DEFAULT_BRANCH}"

- name: Trigger GitHub Pages deployment
# bot の push では workflow が連鎖しないので、明示的に叩く。
# サイトのデプロイは Cloudflare Pages の Git 連携が拾う
- name: Trigger the D1 sync
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GH_TOKEN: ${{ github.token }}
run: gh workflow run pages.yml --ref "${DEFAULT_BRANCH}"
run: gh workflow run sync-d1.yml --ref "${DEFAULT_BRANCH}"
6 changes: 4 additions & 2 deletions .github/workflows/merge-similar-notes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,11 @@ jobs:
git pull --rebase origin "${DEFAULT_BRANCH}"
git push origin "HEAD:${DEFAULT_BRANCH}"

- name: Trigger GitHub Pages deployment
# bot の push では workflow が連鎖しないので、明示的に叩く。
# サイトのデプロイは Cloudflare Pages の Git 連携が拾う
- name: Trigger the D1 sync
if: steps.apply.outputs.has_changes == 'true'
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GH_TOKEN: ${{ github.token }}
run: gh workflow run pages.yml --ref "${DEFAULT_BRANCH}"
run: gh workflow run sync-d1.yml --ref "${DEFAULT_BRANCH}"
66 changes: 0 additions & 66 deletions .github/workflows/pages.yml

This file was deleted.

54 changes: 54 additions & 0 deletions .github/workflows/sync-d1.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Sync notes to D1

# サイト本体のデプロイは Cloudflare Pages の Git 連携が push を見て自動で行う。
# この workflow が受け持つのは D1 の更新だけ。
on:
push:
branches: [main]
paths:
- "notes/**"
- "migrations/**"
- "scripts/build-notes-index.mjs"
- "scripts/sync-notes-to-d1.mjs"
- ".github/workflows/sync-d1.yml"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: sync-d1
cancel-in-progress: false

jobs:
sync:
runs-on: ubuntu-latest
timeout-minutes: 10
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
cache: npm

- name: Install dependencies
run: npm ci

# 新しいスキーマを前提にしたコードが動き出す前に DB 側を整える
- name: Apply D1 migrations
run: npx wrangler d1 migrations apply tech-notes --remote -c wrangler.d1.jsonc

- name: Build the notes index
run: node scripts/build-notes-index.mjs

- name: Generate the sync SQL
run: node scripts/sync-notes-to-d1.mjs --out "${RUNNER_TEMP}/sync.sql"

- name: Upsert notes and cards
run: npx wrangler d1 execute tech-notes --remote -c wrangler.d1.jsonc --yes --file="${RUNNER_TEMP}/sync.sql"
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,17 @@ out/
public/data/notes.json
.closed-issue-context.json
npm-debug.log*

# Terraform
infra/.terraform/
infra/*.tfstate
infra/*.tfstate.*
infra/terraform.tfvars
infra/backend.hcl
infra/.terraform.lock.hcl
.wrangler/
tsconfig.tsbuildinfo

# mise のローカル設定(認証情報を置く)
mise.local.toml
.mise.local.toml
31 changes: 25 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,42 @@
- issue をクローズすると、Claude Code Actions が本文と全コメントを読み直して1つの技術ノートへ再構成する
- 後から追加された訂正・補足を反映し、重複した説明や会話形式のやり取りを整理する
- 生成本文と、スクリプトで確定するメタデータを組み合わせて `notes/<ジャンル>/<issue 番号>.md` に保存する
- GitHub Pages ではカードを左右へスワイプするか、左右の矢印キーで次のノートへ進める
- カードを左右へスワイプするか、左右の矢印キーで次のノートへ進める
- 全カードを一巡するまでは同じカードを再表示せず、一巡後に再シャッフルする

サイトは Cloudflare Pages でホストする。カードの閲覧は誰でもできる公開ページで、
学習機能(`/study` と `/api`)だけを Cloudflare Access で自分ひとりに絞っている。

ローカルで確認する場合:

```bash
npm install
npm run dev
```

### GitHub Pages の初期設定
## 暗記モード

`/study` は Anki のような間隔反復(SRS)で出題する画面。タイトルだけを見て中身を思い出し、
答え合わせをしてから左右にスワイプする。

- **右スワイプ = 覚えている**(FSRS の Good)。安定度が伸び、次の出題まで間隔が開く
- **左スワイプ = 覚えていない**(FSRS の Again)。安定度が縮み、当日中にもう一度出る

スケジューラは Anki 23.10 以降の既定アルゴリズムである FSRS-6(`ts-fsrs`)。
学習状態は Cloudflare D1 に保存する。オフラインでもスワイプでき、復帰時にまとめて送られる。

設計の全体は [docs/setup-cloudflare.md](docs/setup-cloudflare.md) と、リポジトリ内の設計メモを参照。

### Cloudflare の初期設定

1. リポジトリの **Settings → Pages** を開く
2. **Build and deployment → Source** で **GitHub Actions** を選ぶ
3. `Deploy GitHub Pages` workflow を手動実行するか、サイト関連ファイルを `main` にマージする
[docs/setup-cloudflare.md](docs/setup-cloudflare.md) を参照。ダッシュボードでの操作が要るのは
アカウントと Zero Trust の初期設定、API トークンの発行、GitHub App の連携まで。
インフラそのものは `infra/` の Terraform で作る。

通常の push では `.github/workflows/pages.yml` がサイトをデプロイする。issue のクローズ時は `.github/workflows/archive-closed-issue.yml` が Markdown の保存、コミット、サイトの再ビルド、デプロイまでを行う。
サイトのデプロイは Cloudflare Pages の Git 連携が push を見て自動で行う。
`notes/` が変わったときは `.github/workflows/sync-d1.yml` が D1 のマイグレーションと
ノートの同期を行う。issue のクローズ時は `.github/workflows/archive-closed-issue.yml` が
Markdown の保存とコミットまでを行い、その push が上記 2 つを起こす。

## ラベル

Expand Down
98 changes: 98 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -1440,3 +1440,101 @@ a {
transition-duration: 0.01ms !important;
}
}

/* ------------------------------------------------------------------ *
* 暗記モード (/study)
* 閲覧用のカードと同じ骨格を使い、想起 → 答え合わせ → 評価の導線だけ足す
* ------------------------------------------------------------------ */

.study-status-right {
display: flex;
align-items: center;
gap: 8px;
}

.study-offline {
color: var(--md-sys-color-on-secondary-container);
background: var(--md-sys-color-secondary-container);
}

/* 暗記度。0〜100 を 5 段階に丸めて色を変える */
.study-mastery {
display: inline-flex;
align-items: center;
gap: 8px;
color: var(--md-sys-color-on-surface-variant);
font-size: var(--md-sys-typescale-label-small-size);
font-variant-numeric: tabular-nums;
}

.study-mastery-bar {
display: block;
width: 64px;
height: 6px;
overflow: hidden;
border-radius: var(--md-sys-shape-corner-full);
background: var(--md-sys-color-surface-container-highest);
}

.study-mastery-bar > span {
display: block;
height: 100%;
border-radius: inherit;
background: var(--md-sys-color-primary);
transition: width var(--md-sys-motion-duration-medium) var(--md-sys-motion-easing-standard);
}

.study-mastery-bar[data-level="0"] > span,
.study-mastery-bar[data-level="1"] > span {
background: var(--md-sys-color-secondary);
}

.study-mastery-bar[data-level="4"] > span {
background: var(--md-sys-color-tertiary);
}

/* 表面: タイトルだけを見せて思い出してもらう */
.study-prompt {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 14px;
min-height: 220px;
text-align: center;
color: var(--md-sys-color-on-surface-variant);
}

.study-prompt small {
color: var(--md-sys-color-outline);
font-size: var(--md-sys-typescale-label-small-size);
}

/* 裏面: ノート本文。長いので中だけスクロールさせる */
.study-answer {
max-height: min(52vh, 520px);
overflow-y: auto;
overscroll-behavior: contain;
}

.study-missing {
color: var(--md-sys-color-on-surface-variant);
}

.study-forgot:not(:disabled) {
color: var(--md-sys-color-error);
border-color: var(--md-sys-color-error);
}

.study-remembered:disabled,
.study-forgot:disabled {
cursor: not-allowed;
opacity: 0.38;
}

.study-message {
margin: 12px auto 0;
color: var(--md-sys-color-on-surface-variant);
font-size: var(--md-sys-typescale-body-small-size);
text-align: center;
}
21 changes: 21 additions & 0 deletions app/study/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { SiteHeader } from "@/components/site-header";
import { StudyDeck } from "@/components/study-deck";

export const metadata = {
title: "Tech Notes 暗記",
};

export default function StudyPage() {
return (
<main className="app-shell">
<SiteHeader />

<StudyDeck />

<footer className="app-footer">
<span>RECALL → GRADE → SCHEDULE</span>
<span>覚えていれば右、思い出せなければ左。</span>
</footer>
</main>
);
}
Loading