@ripla/godd-mcp 1.0.7-canary.8 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +523 -124
- package/dist/godd.cjs +293 -244
- package/dist/godd.js +1005 -321
- package/dist/index.js +148 -119
- package/notes/app/DESIGN.md +5 -2
- package/notes/app/e2e/fixtures.ts +39 -0
- package/notes/app/e2e/ja-typography-visual.spec.ts +185 -0
- package/notes/app/e2e/ja-typography-visual.spec.ts-snapshots/ja-typography-desktop-1280-chromium-linux.png +0 -0
- package/notes/app/e2e/ja-typography-visual.spec.ts-snapshots/ja-typography-mobile-360-chromium-linux.png +0 -0
- package/notes/app/e2e/rtl-logical-properties.spec.ts +36 -0
- package/notes/app/package-lock.json +0 -8
- package/notes/app/package.json +0 -1
- package/notes/app/playwright.config.ts +7 -0
- package/notes/app/pnpm-lock.yaml +0 -8
- package/notes/app/src/components/BranchSelectorModal.tsx +8 -8
- package/notes/app/src/components/CommentPanel.tsx +5 -5
- package/notes/app/src/components/DiffPreview.tsx +4 -4
- package/notes/app/src/components/FileContentView.tsx +1 -1
- package/notes/app/src/components/FileContextMenu.tsx +1 -1
- package/notes/app/src/components/FileInfoPanel.branch-tab.test.tsx +3 -35
- package/notes/app/src/components/IssueCommentThread.test.tsx +40 -19
- package/notes/app/src/components/IssueDetailView.test.tsx +3 -40
- package/notes/app/src/components/IssueList.tsx +3 -3
- package/notes/app/src/components/LabelSelector.tsx +1 -1
- package/notes/app/src/components/MarkdownEditor.tsx +1 -1
- package/notes/app/src/components/Skeleton.tsx +2 -2
- package/notes/app/src/components/ToastContainer.tsx +1 -1
- package/notes/app/src/components/TreeNode.tsx +6 -6
- package/notes/app/src/contexts/ToastContext.tsx +1 -1
- package/notes/app/src/hooks/useComments.ts +1 -1
- package/notes/app/src/hooks/useEditSession.ts +1 -1
- package/notes/app/src/lib/api.ts +1 -13
- package/notes/app/src/lib/csv-utils.ts +1 -1
- package/notes/app/src/lib/postLoginRedirect.ts +1 -1
- package/notes/app/src/lib/rtl-logical-properties.test.ts +59 -0
- package/notes/app/src/lib/user-colors.ts +1 -1
- package/notes/app/src/pages/MainPage.tsx +2 -2
- package/notes/app/src/pages/SettingsPage.test.tsx +40 -10
- package/notes/app/src/pages/SettingsPage.tsx +1 -1
- package/notes/app/src/pages/UserManagementPage.tsx +7 -7
- package/notes/app/src/styles/globals.css +17 -0
- package/notes/app/src/test/mockPeriodicSync.ts +68 -0
- package/notes/app/src/test/mocks.ts +1 -1
- package/package.json +7 -3
- package/templates/feedback/feedback-lambda/index.mjs +342 -0
- package/templates/feedback/frontend/FeedbackWidget.tsx +574 -0
- package/templates/feedback/frontend/api.ts +38 -0
- package/templates/feedback/frontend/composePinImage.ts +80 -0
- package/templates/feedback/frontend/config.ts +14 -0
- package/templates/feedback/frontend/types.ts +41 -0
- package/templates/feedback/terraform/Makefile +43 -0
- package/templates/feedback/terraform/backend.tf +10 -0
- package/templates/feedback/terraform/lambda.tf +95 -0
- package/templates/feedback/terraform/local.tf +12 -0
- package/templates/feedback/terraform/output.tf +9 -0
- package/templates/feedback/terraform/provider.tf +24 -0
- package/templates/feedback/terraform/variables.tf +25 -0
- package/templates/github-actions/aws/deploy-notes.yml.hbs +193 -10
- package/templates/notes-docs/001_project/README.md +0 -2
- package/templates/notes-docs/002_business_flow/README.md +19 -0
- package/templates/notes-docs/003_requirements/README.md +20 -0
- package/templates/notes-docs/003_requirements/questions.csv +1 -0
- package/templates/notes-docs/003_requirements/spec/README.md +11 -0
- package/templates/notes-docs/004_pages/README.md +22 -0
- package/templates/notes-docs/005_architecture/README.md +20 -0
- package/templates/notes-docs/006_development_workflow/README.md +67 -0
- package/templates/notes-docs/006_development_workflow/daily_flow.template.md +303 -0
- package/templates/notes-docs/006_development_workflow/git.template.md +72 -20
- package/templates/notes-docs/006_development_workflow/judgment.template.md +182 -0
- package/templates/notes-docs/006_development_workflow/workflow.template.md +167 -192
- package/templates/notes-docs/007_guides/README.md +85 -0
- package/templates/notes-docs/007_guides/bot-token-setup.md +174 -0
- package/templates/notes-docs/007_guides/getting_started.md +112 -0
- package/templates/notes-docs/007_guides/glossary.md +145 -0
- package/templates/notes-docs/007_guides/godd_notes_features.md +105 -0
- package/templates/notes-docs/007_guides/godd_notes_usage.md +28 -19
- package/templates/notes-docs/007_guides/godd_tools.md +129 -0
- package/templates/notes-docs/007_guides/godd_usage.md +88 -95
- package/templates/notes-docs/007_guides/infra/README.md +57 -0
- package/templates/notes-docs/007_guides/{setup_aws_accounts.md → infra/setup_aws_accounts.md} +3 -2
- package/templates/notes-docs/007_guides/{setup_infra.template.md → infra/setup_infra.template.md} +15 -4
- package/templates/notes-docs/007_guides/setup_godd.md +73 -22
- package/templates/notes-docs/007_guides/setup_godd_notes.md +97 -42
- package/templates/notes-docs/007_guides/setup_godd_notes.md.styles.json +2 -2
- package/templates/notes-docs/007_guides/setup_local_environment.template.md +81 -20
- package/templates/notes-docs/007_guides/troubleshooting.md +225 -0
- package/templates/notes-docs/999_ref/README.md +23 -0
- package/templates/notes-docs/GENERATED.md +1 -1
- package/templates/notes-docs/README.md +64 -34
- package/templates/terraform/aws/iam.tf.hbs +361 -4
- /package/templates/notes-docs/007_guides/{setup_aws_cost_alert.md → infra/setup_aws_cost_alert.md} +0 -0
- /package/templates/notes-docs/007_guides/{setup_aws_guard_duty.md → infra/setup_aws_guard_duty.md} +0 -0
package/notes/app/DESIGN.md
CHANGED
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
| ホバー | `transition-colors` |
|
|
121
121
|
| ローディング(スケルトン) | `animate-pulse` |
|
|
122
122
|
| ローディング(スピナー) | `animate-spin` |
|
|
123
|
-
| パネル登場 | `animate-slide-in
|
|
123
|
+
| パネル登場 | `animate-slide-in`(LTR は `translateX(100%)→0`、RTL は `translateX(-100%)→0`。0.25s ease-out。`globals.css` で定義) |
|
|
124
124
|
|
|
125
125
|
---
|
|
126
126
|
|
|
@@ -196,6 +196,9 @@
|
|
|
196
196
|
> 日本語本文・見出し・和欧混植・改行・フォーム密度を AI/人間が壊さないための契約を固定する。
|
|
197
197
|
> §1–8(TypeUI ベースの色・状態・レイアウト契約)と矛盾せず**補完**する。§9.2–9.3 の recipes は
|
|
198
198
|
> #1020 で `notes/app/src/styles/globals.css` に実装済み(長文プローズ `max-width` のみ保留 — §9.3 参照。本書はトークン・ルールの正本)。
|
|
199
|
+
> 見出しの語中改行禁止・本文 line-height・長い URL/パスの折り返しは、実ブラウザの視覚回帰 E2E
|
|
200
|
+
> (`notes/app/e2e/ja-typography-visual.spec.ts`:モバイル 360px / デスクトップ 1280px、`toHaveScreenshot` +
|
|
201
|
+
> `Range.getClientRects` による幾何アサーション)で退行を自動検出する(#1065)。
|
|
199
202
|
|
|
200
203
|
### 9.1 タイポグラフィ役割スケール(日本語前提)
|
|
201
204
|
|
|
@@ -250,7 +253,7 @@
|
|
|
250
253
|
| 本文 `line-height` ≥ 1.5 | ✅ PASS | `globals.css` の `:lang(ja) :is(p,li,dd)` で `line-height: 1.55` を明示(#1020) |
|
|
251
254
|
| 見出しと本文の行間ロジック分離 | ✅ PASS | 見出し `h1:1.35 / h2:1.4 / h3,h4:1.45` を本文(1.55)と分離して明示(#1020 / §9.1 スケール準拠) |
|
|
252
255
|
| テーブル / フォームの本文からの分離 | ✅ PASS | 本文レシピは `p,li,dd` にスコープし `table` は非継承。フォームは `label/input/textarea/select/button` に個別ルール(入力系のみ `line-height: 1.5`)を明示(#1020 / §9.3) |
|
|
253
|
-
| 和欧混植見出しのモバイル目視検証 | ✅ PASS | 固定 360px 幅(モバイル相当)で Windows/Chromium 実機確認済み(#1020)。H1/H2 の和欧混植(`GoDD Notes` / `API Gateway` / `OAuth 2.0`)が語中改行せず折り返し、長い URL /
|
|
256
|
+
| 和欧混植見出しのモバイル目視検証 | ✅ PASS | 固定 360px 幅(モバイル相当)で Windows/Chromium 実機確認済み(#1020)。H1/H2 の和欧混植(`GoDD Notes` / `API Gateway` / `OAuth 2.0`)が語中改行せず折り返し、長い URL / パスもコンテナ内で折り返し横溢れなし。以降の退行は手動目視ではなく `notes/app/e2e/ja-typography-visual.spec.ts`(モバイル 360px / デスクトップ 1280px、Docker `notes-e2e-test` で自動実行)が検出する(#1065) |
|
|
254
257
|
| macOS / Windows レンダリング確認 | 🟡 WARN | Windows/Chromium は実機確認済み(#1020)。使用プロパティは標準(`overflow-wrap: anywhere` は全ブラウザ対応、`word-break: auto-phrase` は `@supports` ガードで安全に段階適用、`text-autospace` は未対応環境で無害に無視)。**macOS 実機の目視のみ未実施**(当該環境が手元になく AI では実行不能・UAT 推奨) |
|
|
255
258
|
| タッチターゲット(44px) | ✅ PASS | §4 / §5 で `min-h-11` を契約・実装済み |
|
|
256
259
|
|
|
@@ -17,6 +17,45 @@ interface Fixtures {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const test = base.extend<Fixtures>({
|
|
20
|
+
// The API intentionally returns the mock tree with `mode: "mock"` when no
|
|
21
|
+
// GitHub App credentials are available. The file-view and panel scenarios
|
|
22
|
+
// still exercise the authenticated editor UI against that deterministic
|
|
23
|
+
// mock data; normalize only the UI mode marker so MainPage exposes the
|
|
24
|
+
// editor controls. Write endpoints remain mock-disabled and are not
|
|
25
|
+
// called by these browser scenarios.
|
|
26
|
+
page: async ({ page }, use) => {
|
|
27
|
+
await page.route("**/api/tree**", async (route) => {
|
|
28
|
+
const response = await route.fetch();
|
|
29
|
+
if (!response.ok()) {
|
|
30
|
+
await route.fulfill({ response });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const data: unknown = await response.json();
|
|
35
|
+
if (
|
|
36
|
+
typeof data === "object" &&
|
|
37
|
+
data !== null &&
|
|
38
|
+
"mode" in data &&
|
|
39
|
+
(data as { mode?: unknown }).mode === "mock"
|
|
40
|
+
) {
|
|
41
|
+
const normalized = { ...(data as Record<string, unknown>), mode: "live" };
|
|
42
|
+
await route.fulfill({
|
|
43
|
+
status: response.status(),
|
|
44
|
+
contentType: "application/json",
|
|
45
|
+
body: JSON.stringify(normalized),
|
|
46
|
+
});
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await route.fulfill({
|
|
51
|
+
status: response.status(),
|
|
52
|
+
contentType: "application/json",
|
|
53
|
+
body: JSON.stringify(data),
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await use(page);
|
|
58
|
+
},
|
|
20
59
|
loginPage: async ({ page }, use) => {
|
|
21
60
|
await use(new LoginPage(page));
|
|
22
61
|
},
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { test, expect, type Locator, type Page } from "./fixtures";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* #1065: 日本語組版契約(`notes/app/DESIGN.md` §9.1–9.3 / `src/styles/globals.css`)の
|
|
5
|
+
* 視覚回帰テスト。PR #1064 のレビュー指摘(本 PR スコープ外の Suggestion / #1020 フォローアップ)を追跡する。
|
|
6
|
+
*
|
|
7
|
+
* 対象(§9.1–9.3、issue #1065 のスコープ):
|
|
8
|
+
* - 見出し(h1–h3)の和欧混植(`GoDD Notes` / `API Gateway` / `OAuth 2.0`)が語中で改行しないこと
|
|
9
|
+
* - 本文(p)の line-height が契約範囲 1.5–1.65(既定 1.55)内であること
|
|
10
|
+
* - 長い URL / パスがコンテナ内で折り返し、横溢れしないこと
|
|
11
|
+
* スコープ外: 長文プローズ `max-width: 42em`(§9.3 で実装意図的に保留。要否は 質問リスト.md / #1020 で追跡)。
|
|
12
|
+
*
|
|
13
|
+
* 検証方針(3 段構え):
|
|
14
|
+
* 1. 見出しの `computed style`(`word-break`)が `break-all`(§9.4 の禁止事項)に退行していないことを
|
|
15
|
+
* 直接検証する。短い和欧混植トークンは行幅に余裕があると break-all 下でも偶然折り返し位置が
|
|
16
|
+
* 変わらないことがあるため、レンダリング結果だけに頼らない一次検証として置く。
|
|
17
|
+
* 2. 幾何アサーション(`Range.getClientRects`)で「語中改行しない」ことを実測する。
|
|
18
|
+
* PR #1041(`panel-resize-handle.spec.ts`)の `getBoundingClientRect` 数値比較パターンを踏襲し、
|
|
19
|
+
* クロス OS のフォント描画差に左右されにくい決定的な二次検証とする。
|
|
20
|
+
* 3. Playwright `toHaveScreenshot` でプロース領域のピクセル差分も取得し、issue #1065 の受け入れ基準
|
|
21
|
+
* (ベースライン確定後に `globals.css` の組版 recipe を変更すると視覚回帰テストが fail すること)を満たす。
|
|
22
|
+
*
|
|
23
|
+
* 対象コンテンツは実際の本番レンダリングパス(`FileContentView` → `MarkdownView` の
|
|
24
|
+
* marked + `.prose`)を通す。API 応答のみ `page.route` でモックする(`file-view.spec.ts` と同じ方針)。
|
|
25
|
+
*
|
|
26
|
+
* ベースラインスナップショット(`ja-typography-visual.spec.ts-snapshots/*.png`)は
|
|
27
|
+
* Docker(`notes-e2e-test` / Linux + Playwright 同梱 Chromium 固定)でのみ生成・更新すること。
|
|
28
|
+
* ローカル Windows/macOS で `--update-snapshots` を実行すると、フォントレンダリング差で
|
|
29
|
+
* CI のベースラインと乖離し無効化される(issue #1065 の留意点)。
|
|
30
|
+
*
|
|
31
|
+
* docker compose --profile test run --rm notes-app-test pnpm build
|
|
32
|
+
* docker compose up --build -d postgres notes-api notes-app
|
|
33
|
+
* docker compose --profile e2e run --rm -v "$(pwd)/notes/app/e2e:/app/e2e" notes-e2e-test \
|
|
34
|
+
* npx playwright test e2e/ja-typography-visual.spec.ts --update-snapshots
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
const FIXTURE_PATH = "e2e-fixtures/ja-typography-contract.md";
|
|
38
|
+
|
|
39
|
+
// DESIGN.md §9 の和欧混植サンプル(GoDD Notes / API Gateway / OAuth 2.0)+ 長い URL/パス。
|
|
40
|
+
const MARKDOWN_FIXTURE = `# GoDD Notes と API Gateway 連携
|
|
41
|
+
|
|
42
|
+
## API Gateway 経由のリクエストフロー
|
|
43
|
+
|
|
44
|
+
### OAuth 2.0 によるトークン発行
|
|
45
|
+
|
|
46
|
+
このセクションでは、GoDD Notes が API Gateway を経由して外部サービスと連携する際の認証フローを説明します。認証には OAuth 2.0 を使用し、アクセストークンを発行します。
|
|
47
|
+
|
|
48
|
+
トークンエンドポイントの例です。長い URL はコンテナ内で折り返され、横溢れしません。
|
|
49
|
+
|
|
50
|
+
https://api.example.com/v1/oauth/token/exchange?client_id=godd-notes-app&scope=read-write-admin&redirect_uri=https%3A%2F%2Fnotes.example.com%2Fcallback
|
|
51
|
+
|
|
52
|
+
設定ファイルのパス例: /var/lib/godd/config/notes-api-gateway-oauth-client-credentials.json
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
async function mockFixtureFile(page: Page): Promise<void> {
|
|
56
|
+
await page.route(`**/api/files/${FIXTURE_PATH}`, async (route) => {
|
|
57
|
+
await route.fulfill({
|
|
58
|
+
status: 200,
|
|
59
|
+
contentType: "application/json",
|
|
60
|
+
body: JSON.stringify({
|
|
61
|
+
path: FIXTURE_PATH,
|
|
62
|
+
content: MARKDOWN_FIXTURE,
|
|
63
|
+
sha: "e2e-fixture-sha",
|
|
64
|
+
size: MARKDOWN_FIXTURE.length,
|
|
65
|
+
encoding: "utf-8",
|
|
66
|
+
styles: null,
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* `heading` 内で `token` が単一行に収まっている(= 語中改行していない)ことを検証する。
|
|
74
|
+
* `Range.getClientRects()` の `top` 座標がすべて同じなら、その文字列は 1 行に収まっている。
|
|
75
|
+
* `word-break: break-all` 等への回帰でトークン内に改行が入ると `top` が複数値に分かれ fail する。
|
|
76
|
+
*/
|
|
77
|
+
async function assertNoMidWordWrap(heading: Locator, token: string): Promise<void> {
|
|
78
|
+
const lineCount = await heading.evaluate((el, needle: string) => {
|
|
79
|
+
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
80
|
+
let node: Text | null;
|
|
81
|
+
while ((node = walker.nextNode() as Text | null)) {
|
|
82
|
+
const text = node.textContent ?? "";
|
|
83
|
+
const idx = text.indexOf(needle);
|
|
84
|
+
if (idx === -1) continue;
|
|
85
|
+
const range = document.createRange();
|
|
86
|
+
range.setStart(node, idx);
|
|
87
|
+
range.setEnd(node, idx + needle.length);
|
|
88
|
+
const tops = Array.from(range.getClientRects()).map((r) => Math.round(r.top));
|
|
89
|
+
return new Set(tops).size;
|
|
90
|
+
}
|
|
91
|
+
return 0;
|
|
92
|
+
}, token);
|
|
93
|
+
|
|
94
|
+
expect(lineCount, `"${token}" は語中改行せず単一行に収まる必要があります`).toBe(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function runTypographyContractAssertions(page: Page): Promise<void> {
|
|
98
|
+
const prose = page.locator("main .prose");
|
|
99
|
+
await expect(prose).toBeVisible({ timeout: 10_000 });
|
|
100
|
+
|
|
101
|
+
const h1 = prose.locator("h1");
|
|
102
|
+
const h2 = prose.locator("h2");
|
|
103
|
+
const h3 = prose.locator("h3");
|
|
104
|
+
await expect(h1).toBeVisible();
|
|
105
|
+
await expect(h2).toBeVisible();
|
|
106
|
+
await expect(h3).toBeVisible();
|
|
107
|
+
|
|
108
|
+
await test.step("見出しに本文へのグローバル break-all が波及していない(DESIGN.md §9.4 禁止事項)", async () => {
|
|
109
|
+
// 短い和欧混植トークン(GoDD / API 等)は現行フォント・ビューポート幅では
|
|
110
|
+
// word-break: break-all に規制退行しても偶然折り返し位置が変わらないことがあるため
|
|
111
|
+
// (語自体が行に収まってしまう)、レンダリング結果だけに頼らず computed style も直接検証する。
|
|
112
|
+
for (const heading of [h1, h2, h3]) {
|
|
113
|
+
const wordBreak = await heading.evaluate((el) => getComputedStyle(el).wordBreak);
|
|
114
|
+
expect(wordBreak, "見出しに word-break: break-all が適用されてはならない").not.toBe(
|
|
115
|
+
"break-all",
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
await test.step("見出しが和欧混植で語中改行しない(DESIGN.md §9.3)", async () => {
|
|
121
|
+
await assertNoMidWordWrap(h1, "GoDD");
|
|
122
|
+
await assertNoMidWordWrap(h1, "Notes");
|
|
123
|
+
await assertNoMidWordWrap(h1, "API");
|
|
124
|
+
await assertNoMidWordWrap(h1, "Gateway");
|
|
125
|
+
await assertNoMidWordWrap(h2, "Gateway");
|
|
126
|
+
await assertNoMidWordWrap(h3, "OAuth");
|
|
127
|
+
await assertNoMidWordWrap(h3, "2.0");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
await test.step("本文 line-height が契約範囲 1.5–1.65 内(DESIGN.md §9.1)", async () => {
|
|
131
|
+
const metrics = await prose.locator("p").first().evaluate((el) => {
|
|
132
|
+
const style = getComputedStyle(el);
|
|
133
|
+
return {
|
|
134
|
+
lineHeight: Number.parseFloat(style.lineHeight),
|
|
135
|
+
fontSize: Number.parseFloat(style.fontSize),
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
const ratio = metrics.lineHeight / metrics.fontSize;
|
|
139
|
+
expect(ratio).toBeGreaterThanOrEqual(1.5);
|
|
140
|
+
expect(ratio).toBeLessThanOrEqual(1.65);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
await test.step("長い URL / パスがコンテナ内で折り返し横溢れしない(DESIGN.md §9.3)", async () => {
|
|
144
|
+
const overflow = await prose.evaluate((el) => el.scrollWidth - el.clientWidth);
|
|
145
|
+
expect(overflow).toBeLessThanOrEqual(1);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
test.describe("日本語組版契約の視覚回帰 (#1065)", () => {
|
|
150
|
+
test.describe("モバイル 360px", () => {
|
|
151
|
+
test.use({ viewport: { width: 360, height: 800 } });
|
|
152
|
+
|
|
153
|
+
test("見出し折り返し・本文 line-height・長文 URL 折り返しが契約どおり", async ({
|
|
154
|
+
page,
|
|
155
|
+
mainPage,
|
|
156
|
+
}) => {
|
|
157
|
+
await mockFixtureFile(page);
|
|
158
|
+
await mainPage.goto(FIXTURE_PATH);
|
|
159
|
+
|
|
160
|
+
await runTypographyContractAssertions(page);
|
|
161
|
+
|
|
162
|
+
await expect(page.locator("main .prose")).toHaveScreenshot(
|
|
163
|
+
"ja-typography-mobile-360.png",
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test.describe("デスクトップ 1280px", () => {
|
|
169
|
+
test.use({ viewport: { width: 1280, height: 800 } });
|
|
170
|
+
|
|
171
|
+
test("見出し折り返し・本文 line-height・長文 URL 折り返しが契約どおり", async ({
|
|
172
|
+
page,
|
|
173
|
+
mainPage,
|
|
174
|
+
}) => {
|
|
175
|
+
await mockFixtureFile(page);
|
|
176
|
+
await mainPage.goto(FIXTURE_PATH);
|
|
177
|
+
|
|
178
|
+
await runTypographyContractAssertions(page);
|
|
179
|
+
|
|
180
|
+
await expect(page.locator("main .prose")).toHaveScreenshot(
|
|
181
|
+
"ja-typography-desktop-1280.png",
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
});
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { test, expect } from "./fixtures";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* #1043: TreeNode の階層インデントが dir に応じて inline-start 側へ移ることを
|
|
5
|
+
* 実ブラウザで検証する。jsdom は論理プロパティの物理側を計算できないため、
|
|
6
|
+
* paddingInlineStart の反転は E2E でガードする。
|
|
7
|
+
*/
|
|
8
|
+
test.describe("RTL 論理プロパティ (#1043)", () => {
|
|
9
|
+
test.use({ viewport: { width: 1280, height: 720 } });
|
|
10
|
+
|
|
11
|
+
test("ファイルツリーの階層インデントが RTL で物理右へ反転する", async ({ page, mainPage }) => {
|
|
12
|
+
await mainPage.goto();
|
|
13
|
+
await page.getByRole("button", { name: /001_project/ }).click();
|
|
14
|
+
|
|
15
|
+
const nestedFile = page.getByRole("button", { name: /features\.csv/ });
|
|
16
|
+
await expect(nestedFile).toBeVisible();
|
|
17
|
+
|
|
18
|
+
const measurePadding = () =>
|
|
19
|
+
nestedFile.evaluate((element) => {
|
|
20
|
+
const style = getComputedStyle(element);
|
|
21
|
+
return {
|
|
22
|
+
left: Number.parseFloat(style.paddingLeft),
|
|
23
|
+
right: Number.parseFloat(style.paddingRight),
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const ltr = await measurePadding();
|
|
28
|
+
expect(ltr.left).toBeGreaterThan(ltr.right);
|
|
29
|
+
|
|
30
|
+
await page.evaluate(() => document.documentElement.setAttribute("dir", "rtl"));
|
|
31
|
+
|
|
32
|
+
const rtl = await measurePadding();
|
|
33
|
+
expect(rtl.right).toBeGreaterThan(rtl.left);
|
|
34
|
+
expect(rtl.right).toBeCloseTo(ltr.left, 0);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -46,7 +46,6 @@
|
|
|
46
46
|
"@playwright/test": "^1.59.1",
|
|
47
47
|
"@tailwindcss/vite": "^4.1.18",
|
|
48
48
|
"@testing-library/react": "^16.3.2",
|
|
49
|
-
"@types/marked": "^5.0.2",
|
|
50
49
|
"@types/node": "^24.10.1",
|
|
51
50
|
"@types/react": "^19.2.7",
|
|
52
51
|
"@types/react-dom": "^19.2.3",
|
|
@@ -3898,13 +3897,6 @@
|
|
|
3898
3897
|
"@types/mdurl": "^2"
|
|
3899
3898
|
}
|
|
3900
3899
|
},
|
|
3901
|
-
"node_modules/@types/marked": {
|
|
3902
|
-
"version": "5.0.2",
|
|
3903
|
-
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-5.0.2.tgz",
|
|
3904
|
-
"integrity": "sha512-OucS4KMHhFzhz27KxmWg7J+kIYqyqoW5kdIEI319hqARQQUTqhao3M/F+uFnDXD0Rg72iDDZxZNxq5gvctmLlg==",
|
|
3905
|
-
"dev": true,
|
|
3906
|
-
"license": "MIT"
|
|
3907
|
-
},
|
|
3908
3900
|
"node_modules/@types/mdurl": {
|
|
3909
3901
|
"version": "2.0.0",
|
|
3910
3902
|
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
|
package/notes/app/package.json
CHANGED
|
@@ -17,6 +17,13 @@ export default defineConfig({
|
|
|
17
17
|
screenshot: "only-on-failure",
|
|
18
18
|
},
|
|
19
19
|
|
|
20
|
+
// #1065: 日本語組版契約の視覚回帰(toHaveScreenshot)向けの既定閾値。
|
|
21
|
+
// Docker(notes-e2e-test / Linux+bundled Chromium)でベースラインを固定する前提で、
|
|
22
|
+
// フォントのアンチエイリアス差を吸収しつつレイアウト崩れ(語中改行・行間変化)は検出する値。
|
|
23
|
+
expect: {
|
|
24
|
+
toHaveScreenshot: { maxDiffPixelRatio: 0.02, animations: "disabled" },
|
|
25
|
+
},
|
|
26
|
+
|
|
20
27
|
projects: [
|
|
21
28
|
{
|
|
22
29
|
name: "setup",
|
package/notes/app/pnpm-lock.yaml
CHANGED
|
@@ -117,9 +117,6 @@ importers:
|
|
|
117
117
|
'@testing-library/react':
|
|
118
118
|
specifier: ^16.3.2
|
|
119
119
|
version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
|
120
|
-
'@types/marked':
|
|
121
|
-
specifier: ^5.0.2
|
|
122
|
-
version: 5.0.2
|
|
123
120
|
'@types/node':
|
|
124
121
|
specifier: ^24.10.1
|
|
125
122
|
version: 24.10.13
|
|
@@ -1535,9 +1532,6 @@ packages:
|
|
|
1535
1532
|
'@types/markdown-it@14.1.2':
|
|
1536
1533
|
resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}
|
|
1537
1534
|
|
|
1538
|
-
'@types/marked@5.0.2':
|
|
1539
|
-
resolution: {integrity: sha512-OucS4KMHhFzhz27KxmWg7J+kIYqyqoW5kdIEI319hqARQQUTqhao3M/F+uFnDXD0Rg72iDDZxZNxq5gvctmLlg==}
|
|
1540
|
-
|
|
1541
1535
|
'@types/mdurl@2.0.0':
|
|
1542
1536
|
resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
|
|
1543
1537
|
|
|
@@ -5042,8 +5036,6 @@ snapshots:
|
|
|
5042
5036
|
'@types/linkify-it': 5.0.0
|
|
5043
5037
|
'@types/mdurl': 2.0.0
|
|
5044
5038
|
|
|
5045
|
-
'@types/marked@5.0.2': {}
|
|
5046
|
-
|
|
5047
5039
|
'@types/mdurl@2.0.0': {}
|
|
5048
5040
|
|
|
5049
5041
|
'@types/node@24.10.13':
|
|
@@ -82,9 +82,9 @@ export default function BranchSelectorModal({
|
|
|
82
82
|
<button
|
|
83
83
|
type="button"
|
|
84
84
|
onClick={() => { onSelect(null); onClose(); }}
|
|
85
|
-
className={`block w-full text-
|
|
85
|
+
className={`block w-full text-start px-4 py-3 border-b border-gray-700/50 transition-colors ${
|
|
86
86
|
currentBranch === null
|
|
87
|
-
? "bg-blue-900/40 border-
|
|
87
|
+
? "bg-blue-900/40 border-s-2 border-s-blue-400"
|
|
88
88
|
: "hover:bg-gray-700/30"
|
|
89
89
|
}`}
|
|
90
90
|
>
|
|
@@ -92,7 +92,7 @@ export default function BranchSelectorModal({
|
|
|
92
92
|
<GitBranch className="w-3.5 h-3.5 text-green-400 shrink-0" />
|
|
93
93
|
<span className="text-sm font-medium">main</span>
|
|
94
94
|
{currentBranch === null && (
|
|
95
|
-
<span className="text-[10px] text-blue-400
|
|
95
|
+
<span className="text-[10px] text-blue-400 ms-auto">表示中</span>
|
|
96
96
|
)}
|
|
97
97
|
</div>
|
|
98
98
|
</button>
|
|
@@ -120,9 +120,9 @@ export default function BranchSelectorModal({
|
|
|
120
120
|
key={pr.number}
|
|
121
121
|
type="button"
|
|
122
122
|
onClick={() => { onSelect(pr.branch); onClose(); }}
|
|
123
|
-
className={`block w-full text-
|
|
123
|
+
className={`block w-full text-start px-4 py-3 border-b border-gray-700/50 transition-colors ${
|
|
124
124
|
isSelected
|
|
125
|
-
? "bg-blue-900/40 border-
|
|
125
|
+
? "bg-blue-900/40 border-s-2 border-s-blue-400"
|
|
126
126
|
: "hover:bg-gray-700/30"
|
|
127
127
|
}`}
|
|
128
128
|
>
|
|
@@ -137,10 +137,10 @@ export default function BranchSelectorModal({
|
|
|
137
137
|
<span className="text-[10px] text-blue-400 shrink-0">表示中</span>
|
|
138
138
|
)}
|
|
139
139
|
</div>
|
|
140
|
-
<div className="text-[10px] text-gray-500 font-mono truncate mb-0.5
|
|
140
|
+
<div className="text-[10px] text-gray-500 font-mono truncate mb-0.5 ps-4">
|
|
141
141
|
{pr.branch}
|
|
142
142
|
</div>
|
|
143
|
-
<div className="flex items-center gap-2 text-[10px] text-gray-500
|
|
143
|
+
<div className="flex items-center gap-2 text-[10px] text-gray-500 ps-4">
|
|
144
144
|
<span>#{pr.number}</span>
|
|
145
145
|
<span>{pr.author}</span>
|
|
146
146
|
<span>
|
|
@@ -156,7 +156,7 @@ export default function BranchSelectorModal({
|
|
|
156
156
|
href={pr.url}
|
|
157
157
|
target="_blank"
|
|
158
158
|
rel="noopener noreferrer"
|
|
159
|
-
className="text-blue-400 hover:text-blue-300
|
|
159
|
+
className="text-blue-400 hover:text-blue-300 ms-auto"
|
|
160
160
|
onClick={(e) => e.stopPropagation()}
|
|
161
161
|
>
|
|
162
162
|
GitHub
|
|
@@ -79,7 +79,7 @@ function CommentItem({
|
|
|
79
79
|
)}
|
|
80
80
|
</div>
|
|
81
81
|
{comment.anchor_text && (
|
|
82
|
-
<blockquote className="mt-1
|
|
82
|
+
<blockquote className="mt-1 ps-2 border-s-2 border-blue-600 text-gray-400 truncate">
|
|
83
83
|
{comment.anchor_text}
|
|
84
84
|
</blockquote>
|
|
85
85
|
)}
|
|
@@ -185,7 +185,7 @@ function CommentItem({
|
|
|
185
185
|
<button
|
|
186
186
|
type="button"
|
|
187
187
|
onClick={() => onDelete(comment.id)}
|
|
188
|
-
className="flex items-center gap-0.5 hover:text-red-400 transition-colors
|
|
188
|
+
className="flex items-center gap-0.5 hover:text-red-400 transition-colors ms-auto"
|
|
189
189
|
title="削除"
|
|
190
190
|
>
|
|
191
191
|
<Trash2 className="w-3 h-3" />
|
|
@@ -195,7 +195,7 @@ function CommentItem({
|
|
|
195
195
|
|
|
196
196
|
{/* Replies */}
|
|
197
197
|
{expanded && replies.length > 0 && (
|
|
198
|
-
<div className="border-t border-gray-700
|
|
198
|
+
<div className="border-t border-gray-700 ms-4 space-y-0 divide-y divide-gray-700/50">
|
|
199
199
|
{replies.map((reply) => (
|
|
200
200
|
<div key={reply.id} className="px-2.5 py-1.5">
|
|
201
201
|
<div className="flex items-baseline gap-1.5 flex-wrap">
|
|
@@ -208,7 +208,7 @@ function CommentItem({
|
|
|
208
208
|
<button
|
|
209
209
|
type="button"
|
|
210
210
|
onClick={() => onDelete(reply.id)}
|
|
211
|
-
className="flex items-center gap-0.5 hover:text-red-400 transition-colors
|
|
211
|
+
className="flex items-center gap-0.5 hover:text-red-400 transition-colors ms-auto"
|
|
212
212
|
title="削除"
|
|
213
213
|
>
|
|
214
214
|
<Trash2 className="w-3 h-3" />
|
|
@@ -315,7 +315,7 @@ export default function CommentPanel({
|
|
|
315
315
|
<h3 className="text-sm font-semibold text-gray-300">
|
|
316
316
|
コメント
|
|
317
317
|
{allComments.length > 0 && (
|
|
318
|
-
<span className="
|
|
318
|
+
<span className="ms-1.5 text-xs text-gray-500">({allComments.length})</span>
|
|
319
319
|
)}
|
|
320
320
|
</h3>
|
|
321
321
|
<div className="flex items-center gap-1">
|
|
@@ -30,7 +30,7 @@ export default function DiffPreview({ original, modified, onClose }: Props) {
|
|
|
30
30
|
<span className="text-sm font-medium text-gray-300">変更プレビュー</span>
|
|
31
31
|
<span className="text-xs text-green-400">+{stats.added}</span>
|
|
32
32
|
<span className="text-xs text-red-400">-{stats.removed}</span>
|
|
33
|
-
<div role="toolbar" aria-label="差分表示モード" className="flex items-center gap-0.5 rounded bg-gray-700 p-0.5
|
|
33
|
+
<div role="toolbar" aria-label="差分表示モード" className="flex items-center gap-0.5 rounded bg-gray-700 p-0.5 ms-2">
|
|
34
34
|
<button
|
|
35
35
|
type="button"
|
|
36
36
|
aria-pressed={viewMode === "unified"}
|
|
@@ -98,10 +98,10 @@ function UnifiedView({ changes }: { changes: ReturnType<typeof diffLines> }) {
|
|
|
98
98
|
const prefix = part.added ? "+" : part.removed ? "-" : " ";
|
|
99
99
|
return (
|
|
100
100
|
<div key={`${i}-${j}`} className={`flex ${bg}`}>
|
|
101
|
-
<span className="w-12 text-
|
|
101
|
+
<span className="w-12 text-end pe-3 text-gray-600 select-none shrink-0">
|
|
102
102
|
{!part.added && !part.removed ? lineNum : ""}
|
|
103
103
|
</span>
|
|
104
|
-
<span className={`
|
|
104
|
+
<span className={`pe-2 ${part.added ? "text-green-500" : part.removed ? "text-red-500" : "text-gray-600"} select-none`}>
|
|
105
105
|
{prefix}
|
|
106
106
|
</span>
|
|
107
107
|
<span className={textColor}>{line || " "}</span>
|
|
@@ -151,7 +151,7 @@ function SideBySideView({ original, modified }: { original: string; modified: st
|
|
|
151
151
|
|
|
152
152
|
return (
|
|
153
153
|
<div className="flex min-w-0 overflow-x-auto">
|
|
154
|
-
<div className="flex-1 min-w-[200px] border-
|
|
154
|
+
<div className="flex-1 min-w-[200px] border-e border-gray-700 p-4">
|
|
155
155
|
<div className="text-[10px] text-gray-500 mb-2 uppercase">元のバージョン</div>
|
|
156
156
|
{Array.from({ length: maxLines }).map((_, i) => {
|
|
157
157
|
const line = leftLines[i];
|
|
@@ -57,7 +57,7 @@ function CsvTable({ content }: { content: string }) {
|
|
|
57
57
|
{header.map((cell, i) => (
|
|
58
58
|
<th
|
|
59
59
|
key={i}
|
|
60
|
-
className="border border-gray-600 bg-gray-700 px-3 py-2 text-
|
|
60
|
+
className="border border-gray-600 bg-gray-700 px-3 py-2 text-start font-semibold text-gray-200 whitespace-nowrap"
|
|
61
61
|
>
|
|
62
62
|
{cell}
|
|
63
63
|
</th>
|
|
@@ -54,7 +54,7 @@ export default function FileContextMenu({ x, y, isDir, onAction, onClose }: Prop
|
|
|
54
54
|
role="menuitem"
|
|
55
55
|
key={item.action}
|
|
56
56
|
onClick={() => onAction(item.action)}
|
|
57
|
-
className={`w-full text-
|
|
57
|
+
className={`w-full text-start px-3 py-1.5 text-sm hover:bg-gray-700 flex items-center gap-2 ${
|
|
58
58
|
item.action === "delete" ? "text-red-400 hover:text-red-300" : "text-gray-200"
|
|
59
59
|
}`}
|
|
60
60
|
>
|
|
@@ -5,42 +5,10 @@ import type { FileContent } from "@/types";
|
|
|
5
5
|
const triggerPeriodicSync = vi.hoisted(() => vi.fn());
|
|
6
6
|
|
|
7
7
|
vi.mock("@/hooks/usePeriodicSync", () => {
|
|
8
|
+
// vi.mock factory は hoist されるため、共有ヘルパも factory 内で解決する。
|
|
8
9
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- mock factory 制約
|
|
9
|
-
const {
|
|
10
|
-
return
|
|
11
|
-
// 実フック(usePeriodicSync.ts)の即時 sync 挙動を忠実に再現する:
|
|
12
|
-
// onSync は ref 捕捉し、effect 依存は実フックと同じ [enabled, intervalMs, skipInitialSync]。
|
|
13
|
-
// enabled 時の effect setup(初回マウント / enabled false→true 遷移)で onSync を発火し、
|
|
14
|
-
// 初回マウントは skipInitialSync で抑制する。これにより「mount 直後の foreground load 中に
|
|
15
|
-
// 即時 background poll が走る」競合(#934)を再現する。
|
|
16
|
-
usePeriodicSync: ({
|
|
17
|
-
onSync,
|
|
18
|
-
enabled,
|
|
19
|
-
intervalMs,
|
|
20
|
-
skipInitialSync,
|
|
21
|
-
}: {
|
|
22
|
-
onSync: () => void;
|
|
23
|
-
enabled: boolean;
|
|
24
|
-
intervalMs: number;
|
|
25
|
-
skipInitialSync?: boolean;
|
|
26
|
-
}) => {
|
|
27
|
-
const onSyncRef = useRef(onSync);
|
|
28
|
-
const isInitialMountRef = useRef(true);
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
onSyncRef.current = onSync;
|
|
31
|
-
}, [onSync]);
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
triggerPeriodicSync.mockImplementation(() => {
|
|
34
|
-
if (enabled) onSyncRef.current();
|
|
35
|
-
});
|
|
36
|
-
const isInitialMount = isInitialMountRef.current;
|
|
37
|
-
isInitialMountRef.current = false;
|
|
38
|
-
if (!enabled || intervalMs <= 0) return;
|
|
39
|
-
if (skipInitialSync && isInitialMount) return;
|
|
40
|
-
onSyncRef.current();
|
|
41
|
-
}, [enabled, intervalMs, skipInitialSync]);
|
|
42
|
-
},
|
|
43
|
-
};
|
|
10
|
+
const { faithfulPeriodicSyncMock } = require("../test/mockPeriodicSync.ts") as typeof import("../test/mockPeriodicSync");
|
|
11
|
+
return faithfulPeriodicSyncMock(triggerPeriodicSync);
|
|
44
12
|
});
|
|
45
13
|
|
|
46
14
|
vi.mock("@/lib/api", () => ({
|