@squadbase/vantage 0.6.2-dev1.0017a4d → 0.6.2-dev1.02aab78
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/dist/{build-X5NVCQKT.js → build-YOPOGPQB.js} +7 -7
- package/dist/{build-X5NVCQKT.js.map → build-YOPOGPQB.js.map} +1 -1
- package/dist/{check-ZBIH422T.js → check-FBHT6W6J.js} +5 -5
- package/dist/{check-ZBIH422T.js.map → check-FBHT6W6J.js.map} +1 -1
- package/dist/{chunk-7DZGJQ3P.js → chunk-5C2SEDPH.js} +21 -4
- package/dist/chunk-5C2SEDPH.js.map +1 -0
- package/dist/chunk-6OTTGAPO.js +44 -0
- package/dist/chunk-6OTTGAPO.js.map +1 -0
- package/dist/{chunk-2ICNHUG6.js → chunk-7652CGJJ.js} +10 -3
- package/dist/{chunk-2ICNHUG6.js.map → chunk-7652CGJJ.js.map} +1 -1
- package/dist/{chunk-RU6WKEOG.js → chunk-QOXXOFGL.js} +7 -4
- package/dist/chunk-QOXXOFGL.js.map +1 -0
- package/dist/{chunk-WAMZWZJB.js → chunk-R6MTJAHX.js} +6 -6
- package/dist/chunk-R6MTJAHX.js.map +1 -0
- package/dist/{chunk-CEXAQQ5W.js → chunk-S5MDTHH7.js} +180 -18
- package/dist/chunk-S5MDTHH7.js.map +1 -0
- package/dist/{chunk-JKLLFVEU.js → chunk-UWSQUHX6.js} +4 -99
- package/dist/chunk-UWSQUHX6.js.map +1 -0
- package/dist/cli.js +6 -6
- package/dist/components/index.d.ts +8 -4
- package/dist/components/index.js +1 -1
- package/dist/delta-text-CvwBGJ95.d.ts +15 -0
- package/dist/{dev-4XEDTELV.js → dev-GUKQT6U6.js} +7 -7
- package/dist/{dev-4XEDTELV.js.map → dev-GUKQT6U6.js.map} +1 -1
- package/dist/doc/index.d.ts +22 -1
- package/dist/doc/index.js +45 -72
- package/dist/doc/index.js.map +1 -1
- package/dist/{doctor-5E2VEZPK.js → doctor-B2O3GJDU.js} +5 -5
- package/dist/{doctor-5E2VEZPK.js.map → doctor-B2O3GJDU.js.map} +1 -1
- package/dist/{routes-3SGAFDMC.js → routes-PVKNT777.js} +4 -4
- package/dist/{routes-3SGAFDMC.js.map → routes-PVKNT777.js.map} +1 -1
- package/dist/slide/index.d.ts +96 -1
- package/dist/slide/index.js +414 -297
- package/dist/slide/index.js.map +1 -1
- package/dist/table/index.js +1 -1
- package/dist/{upgrade-JHXZBGQS.js → upgrade-7ZXMCTTR.js} +4 -4
- package/dist/{upgrade-JHXZBGQS.js.map → upgrade-7ZXMCTTR.js.map} +1 -1
- package/dist/vite/index.js +3 -3
- package/docs/en/changelog.md +2 -0
- package/docs/en/components.md +4 -2
- package/docs/en/doc/delta-text.md +40 -0
- package/docs/en/doc/doc-notes-and-sources.md +43 -0
- package/docs/en/pages-and-metadata.md +15 -0
- package/docs/en/parts/delta.md +4 -3
- package/docs/en/parts/kpi-card.md +2 -1
- package/docs/en/slide/slide-chart-full.md +48 -0
- package/docs/en/slide/slide-columns.md +46 -0
- package/docs/en/slide/slide-comparison.md +68 -0
- package/docs/en/slide/slide-heading.md +38 -0
- package/docs/en/slide/slide-section.md +44 -0
- package/docs/en/slide/slide-source-line.md +34 -0
- package/docs/index.json +208 -48
- package/docs/ja/changelog.md +2 -0
- package/docs/ja/components.md +4 -2
- package/docs/ja/doc/delta-text.md +40 -0
- package/docs/ja/doc/doc-notes-and-sources.md +43 -0
- package/docs/ja/page-types/slide.md +1 -0
- package/docs/ja/pages-and-metadata.md +15 -0
- package/docs/ja/parts/delta.md +5 -4
- package/docs/ja/parts/kpi-card.md +2 -1
- package/docs/ja/slide/slide-chart-full.md +48 -0
- package/docs/ja/slide/slide-columns.md +46 -0
- package/docs/ja/slide/slide-comparison.md +68 -0
- package/docs/ja/slide/slide-heading.md +38 -0
- package/docs/ja/slide/slide-section.md +44 -0
- package/docs/ja/slide/slide-source-line.md +34 -0
- package/package.json +2 -2
- package/registry/ui/delta.tsx +7 -4
- package/registry/ui/kpi-card.tsx +9 -4
- package/skills/vantage-pitfalls/SKILL.md +5 -0
- package/skills/vantage-slide/SKILL.md +2 -2
- package/templates/AGENTS.md +4 -2
- package/templates/agents/doc.md +3 -0
- package/templates/agents/slide.md +16 -1
- package/theme.css +7 -4
- package/dist/chunk-7DZGJQ3P.js.map +0 -1
- package/dist/chunk-CEXAQQ5W.js.map +0 -1
- package/dist/chunk-JKLLFVEU.js.map +0 -1
- package/dist/chunk-RU6WKEOG.js.map +0 -1
- package/dist/chunk-WAMZWZJB.js.map +0 -1
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# SlideColumns
|
|
2
|
+
|
|
3
|
+
> [部品] スライド本文の 12 カラム配置。SlideColumns と SlideColumn。
|
|
4
|
+
|
|
5
|
+
スライド本文の 12 カラム配置です。`SlideColumns` の中に `SlideColumn` を並べ、それぞれが `span` カラム分を取ります(8 / 4、6 / 6、4 / 4 / 4 など)。グリッドの本文の行に置く**部品**で、1 枚まるごとの block ではありません。
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { SlideColumns, SlideColumn } from "@squadbase/vantage/slide";
|
|
9
|
+
import type { SlideColumnsProps, SlideColumnProps } from "@squadbase/vantage/slide";
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
> [!NOTE]
|
|
13
|
+
> これは **スライドの部品**で、1 枚まるごとの block ではありません。`<Slide>` 内の 12 カラムのグリッドの行として置きます(ルートはキャンバスいっぱい(100%×100%)には広がりません)。寸法はすべて部品の中で決まっているので、足さないでください。デッキの組み方は [スライド](page-types/slide) を参照してください。
|
|
14
|
+
|
|
15
|
+
## props
|
|
16
|
+
|
|
17
|
+
### SlideColumns
|
|
18
|
+
|
|
19
|
+
| Prop | Type | Default | 説明 |
|
|
20
|
+
| --- | --- | --- | --- |
|
|
21
|
+
| `children` | `ReactNode` | | `SlideColumn` を並べる。**必須**。 |
|
|
22
|
+
| `align` | `"start" \| "center" \| "stretch"` | `"stretch"` | 行の中での縦方向の揃え。 |
|
|
23
|
+
|
|
24
|
+
### SlideColumn
|
|
25
|
+
|
|
26
|
+
| Prop | Type | 説明 |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| `span` | `number` | 12 カラムのうち何カラム分を取るか(1〜12)。**必須**。 |
|
|
29
|
+
| `children` | `ReactNode` | 列の中身。**必須**。 |
|
|
30
|
+
|
|
31
|
+
## 使い方
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { SlideColumns, SlideColumn } from "@squadbase/vantage/slide";
|
|
35
|
+
|
|
36
|
+
<SlideColumns align="center">
|
|
37
|
+
<SlideColumn span={8}>{/* グラフ */}</SlideColumn>
|
|
38
|
+
<SlideColumn span={4}>{/* 読み解き */}</SlideColumn>
|
|
39
|
+
</SlideColumns>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 注意
|
|
43
|
+
|
|
44
|
+
- 並べた `span` の合計は 12 にします。
|
|
45
|
+
- 間隔とカラム数はスライドの仕様値で決まっています。
|
|
46
|
+
- 列の中身が列の高さいっぱいに広がるべきときは、その中身側で `height: "100%"` を指定します。
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# SlideComparison
|
|
2
|
+
|
|
3
|
+
> A と B、施策の前と後を 2〜3 列で並べる 1 枚。
|
|
4
|
+
|
|
5
|
+
2〜3 列を横に並べて比べる 1 枚です。各列に見出し・主役の数字(と増減)・要点を置き、下に 1 行の結論(`verdict`)を出せます。列は 12 カラムを等分します(6 / 6 または 4 / 4 / 4)。
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { SlideComparison } from "@squadbase/vantage/slide";
|
|
9
|
+
import type { SlideComparisonProps, SlideComparisonColumn } from "@squadbase/vantage/slide";
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
> [!NOTE]
|
|
13
|
+
> これは **`<Slide>` 1 枚の中身**です。`<Slide>` 自体は持たないので、`<Slide>` の直下に置いてください。ルートはキャンバス(1920×1080)いっぱい(100%)に広がり、`className` は受けません。余白・文字サイズ・グリッドは block の中で決まっている(1920×1080 の 12 カラム)ので、寸法を足さないでください。デッキの組み方は [スライド](page-types/slide) を参照してください。
|
|
14
|
+
|
|
15
|
+
## props
|
|
16
|
+
|
|
17
|
+
| Prop | Type | 説明 |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| `title` | `string` | 結論を 1 文で書いた見出し(アクションタイトル)。**必須**。 |
|
|
20
|
+
| `columns` | `SlideComparisonColumn[]` | 2〜3 列。**必須**。各列は下の表のとおり。 |
|
|
21
|
+
| `verdict` | `string` | どちらが優れるか、何が続くかを 1 行で。 |
|
|
22
|
+
| `asOf` | `string` | 数字を取った日。日付のみ(例: `"2026-10-01"`)。「2026-10-01 時点」と表示される。「時点」は書かない。 |
|
|
23
|
+
| `source` | `string` | 出典(例: `"社内 BI"`)。「出典: 社内 BI」と表示される。 |
|
|
24
|
+
|
|
25
|
+
### SlideComparisonColumn
|
|
26
|
+
|
|
27
|
+
| Prop | Type | Default | 説明 |
|
|
28
|
+
| --- | --- | --- | --- |
|
|
29
|
+
| `heading` | `string` | | その列が何か(「旧プラン」「施策前」)。**必須**。 |
|
|
30
|
+
| `points` | `string[]` | | 短い要点。**必須**。1 列 3 件まで、1 件 24 文字ほどまで。列ごとに同じ種類の要点を同じ順で書く。 |
|
|
31
|
+
| `value` | `number \| null` | | 主役の数字。`null` は「—」。`format` で整形される。 |
|
|
32
|
+
| `format` | `ValueFormat` | | 値の整形(`"currency"` / `"percent"` など)。`delta` の既定の整形にもなる。 |
|
|
33
|
+
| `display` | `string` | | 整形済みの文字列(「1.2 億円」)。`value` より優先される。 |
|
|
34
|
+
| `delta` | `{ value: number \| null; format?: ValueFormat }` | | 主役の数字の増減そのもの(`percent` なら比率)。向きは符号で決まる。`format` を省くと列の `format` を使う。 |
|
|
35
|
+
| `comparisonLabel` | `string` | | `delta` が何との比較かを示す名前(「前月比」「旧プラン比」)。増減の前に薄く出る。 |
|
|
36
|
+
| `betterWhen` | `"up" \| "down"` | `"up"` | `delta` のどちらの向きが良いか。解約率・コストは `"down"`。 |
|
|
37
|
+
|
|
38
|
+
## 使い方
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import { Slide, SlideComparison } from "@squadbase/vantage/slide";
|
|
42
|
+
|
|
43
|
+
<Slide>
|
|
44
|
+
<SlideComparison
|
|
45
|
+
title="新プランは旧プランより解約が少ない"
|
|
46
|
+
columns={[
|
|
47
|
+
{ heading: "旧プラン", value: 0.024, format: "percent", points: ["月額 9,800 円", "導入支援なし"] },
|
|
48
|
+
{
|
|
49
|
+
heading: "新プラン",
|
|
50
|
+
value: 0.018,
|
|
51
|
+
format: "percent",
|
|
52
|
+
delta: { value: -0.006 },
|
|
53
|
+
comparisonLabel: "旧プラン比",
|
|
54
|
+
betterWhen: "down",
|
|
55
|
+
points: ["月額 12,800 円", "導入支援つき"],
|
|
56
|
+
},
|
|
57
|
+
]}
|
|
58
|
+
verdict="導入支援の有無が解約率の差の主因"
|
|
59
|
+
asOf="2026-10-01"
|
|
60
|
+
source="社内 BI"
|
|
61
|
+
/>
|
|
62
|
+
</Slide>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 注意
|
|
66
|
+
|
|
67
|
+
- 列が 2〜3 でない、または要点が多い・長いと、コンソールに警告が出ます。スライドを分けてください。
|
|
68
|
+
- 増減は `DeltaText`(▲ ▼ と「増」「減」つき)で描かれます。極性は `betterWhen` のみで指定します。
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# SlideHeading
|
|
2
|
+
|
|
3
|
+
> [部品] スライド 1 枚の見出し。アクションタイトルと任意のアイブロウ。
|
|
4
|
+
|
|
5
|
+
スライド 1 枚の見出しです。ページの大きさの `h2` で、2 行までに収まります。上に小さなラベル(`eyebrow`、章名など)を添えられます。12 カラムのグリッドの最上段に置く**部品**で、1 枚まるごとの block ではありません。
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { SlideHeading } from "@squadbase/vantage/slide";
|
|
9
|
+
import type { SlideHeadingProps } from "@squadbase/vantage/slide";
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
> [!NOTE]
|
|
13
|
+
> これは **スライドの部品**で、1 枚まるごとの block ではありません。`<Slide>` 内の 12 カラムのグリッドの行として置きます(ルートはキャンバスいっぱい(100%×100%)には広がりません)。寸法はすべて部品の中で決まっているので、足さないでください。デッキの組み方は [スライド](page-types/slide) を参照してください。
|
|
14
|
+
|
|
15
|
+
## props
|
|
16
|
+
|
|
17
|
+
| Prop | Type | 説明 |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| `title` | `string` | 結論を 1 文で書いた見出し(アクションタイトル)。2 行までに切り詰められる。**必須**。 |
|
|
20
|
+
| `eyebrow` | `string` | 見出しの上の短いラベル(「第 2 章 解約」)。 |
|
|
21
|
+
|
|
22
|
+
## 使い方
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { Slide, SlideHeading } from "@squadbase/vantage/slide";
|
|
26
|
+
|
|
27
|
+
<Slide>
|
|
28
|
+
<div style={{ display: "grid", gridTemplateColumns: "repeat(12, 1fr)" }}>
|
|
29
|
+
<SlideHeading title="解約率は 6 月以降、毎月下がり続けている" eyebrow="第 2 章 解約" />
|
|
30
|
+
{/* …SlideColumns など… */}
|
|
31
|
+
</div>
|
|
32
|
+
</Slide>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 注意
|
|
36
|
+
|
|
37
|
+
- 見出しは話題ではなく結論の文で書きます。
|
|
38
|
+
- 置いたグリッドの 12 カラムすべてにまたがります。
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# SlideSection
|
|
2
|
+
|
|
3
|
+
> 章の区切りの 1 枚。章タイトルと、任意で目次。
|
|
4
|
+
|
|
5
|
+
章の区切りに置く 1 枚です。左に章タイトル、`items` を渡すと右に目次(番号つきの一覧)を出し、`current` の章を強調します。
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { SlideSection } from "@squadbase/vantage/slide";
|
|
9
|
+
import type { SlideSectionProps } from "@squadbase/vantage/slide";
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
> [!NOTE]
|
|
13
|
+
> これは **`<Slide>` 1 枚の中身**です。`<Slide>` 自体は持たないので、`<Slide>` の直下に置いてください。ルートはキャンバス(1920×1080)いっぱい(100%)に広がり、`className` は受けません。余白・文字サイズ・グリッドは block の中で決まっている(1920×1080 の 12 カラム)ので、寸法を足さないでください。デッキの組み方は [スライド](page-types/slide) を参照してください。
|
|
14
|
+
|
|
15
|
+
## props
|
|
16
|
+
|
|
17
|
+
| Prop | Type | 説明 |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| `title` | `string` | 章のタイトル。**必須**。 |
|
|
20
|
+
| `index` | `number` | 章番号。タイトルの上に「02」のように 2 桁で出る。 |
|
|
21
|
+
| `lead` | `string` | その章が何に答えるかを 1 行で。 |
|
|
22
|
+
| `items` | `string[]` | デッキの章の一覧。右側に番号つきの目次として出る。1 行ずつなので 6 件ほどまで。省くとタイトルが全幅を使う。 |
|
|
23
|
+
| `current` | `number` | `items` のうち強調する章(1 始まり)。 |
|
|
24
|
+
|
|
25
|
+
## 使い方
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { Slide, SlideSection } from "@squadbase/vantage/slide";
|
|
29
|
+
|
|
30
|
+
<Slide>
|
|
31
|
+
<SlideSection
|
|
32
|
+
index={2}
|
|
33
|
+
title="解約の動向"
|
|
34
|
+
lead="どの層が、いつ離れているか"
|
|
35
|
+
items={["全体像", "解約の動向", "要因の分析", "次の打ち手"]}
|
|
36
|
+
current={2}
|
|
37
|
+
/>
|
|
38
|
+
</Slide>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 注意
|
|
42
|
+
|
|
43
|
+
- 段階表示(reveal)は持ちません。必要なら呼び出し側で `Steps` などで包んでください。
|
|
44
|
+
- 目次に出る番号は `items` の順で自動採番されます。`index` は章番号の表示だけに使われます。
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# SlideSourceLine
|
|
2
|
+
|
|
3
|
+
> [部品] スライド 1 枚の足元の 1 行。時点・出典・ページ番号。
|
|
4
|
+
|
|
5
|
+
スライド 1 枚の足元に置く 1 行です。データの時点と出典、任意で右端にページ番号を出します。12 カラムのグリッドの最下段に置く**部品**で、1 枚まるごとの block ではありません。出すものがなければ何も描きません。
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { SlideSourceLine } from "@squadbase/vantage/slide";
|
|
9
|
+
import type { SlideSourceLineProps } from "@squadbase/vantage/slide";
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
> [!NOTE]
|
|
13
|
+
> これは **スライドの部品**で、1 枚まるごとの block ではありません。`<Slide>` 内の 12 カラムのグリッドの行として置きます(ルートはキャンバスいっぱい(100%×100%)には広がりません)。寸法はすべて部品の中で決まっているので、足さないでください。デッキの組み方は [スライド](page-types/slide) を参照してください。
|
|
14
|
+
|
|
15
|
+
## props
|
|
16
|
+
|
|
17
|
+
| Prop | Type | 説明 |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| `asOf` | `string` | 数字を取った日。日付のみ(例: `"2026-10-20"`)。「2026-10-20 時点」と表示される。「時点」は書かない。 |
|
|
20
|
+
| `source` | `string` | 出典(例: `"社内 BI"`)。「出典: 社内 BI」と表示される。 |
|
|
21
|
+
| `page` | `{ current: number; total: number }` | 右端に「3 / 12」と出すページ番号。呼び出し側から渡す。 |
|
|
22
|
+
|
|
23
|
+
## 使い方
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { SlideSourceLine } from "@squadbase/vantage/slide";
|
|
27
|
+
|
|
28
|
+
<SlideSourceLine asOf="2026-10-20" source="社内 BI" page={{ current: 3, total: 12 }} />
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 注意
|
|
32
|
+
|
|
33
|
+
- ページ番号は SquadKit がスライドの位置を読めないため、`page` で渡します。
|
|
34
|
+
- 「… 時点」「出典: …」は固定の日本語です。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@squadbase/vantage",
|
|
3
|
-
"version": "0.6.2-dev1.
|
|
3
|
+
"version": "0.6.2-dev1.02aab78",
|
|
4
4
|
"description": "Data dashboard framework for Squadbase Editor. Vite-powered, config-free, one-file first.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
"vitest": "^5.0.5",
|
|
153
153
|
"@squadbase/squadkit": "0.0.0"
|
|
154
154
|
},
|
|
155
|
-
"gitHead": "
|
|
155
|
+
"gitHead": "02aab78e1f5fcbb0c508300dcb39825ca26b23a0",
|
|
156
156
|
"scripts": {
|
|
157
157
|
"build": "node scripts/build-docs.mjs && node scripts/build.mjs",
|
|
158
158
|
"build:js": "tsup --no-dts",
|
package/registry/ui/delta.tsx
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Direction, rate, colour and wording are all derived here from `value` and
|
|
10
10
|
* `comparison` (via `computeComparison`); none of them can be passed in, so they
|
|
11
11
|
* cannot contradict each other. Colour is never the only signal: the arrow and
|
|
12
|
-
* the sign always accompany it. Set `
|
|
12
|
+
* the sign always accompany it. Set `betterWhen="down"` for metrics
|
|
13
13
|
* where a rise is bad (churn, error rate).
|
|
14
14
|
*
|
|
15
15
|
* - A flat change (inside `neutralBand`) uses a right arrow (→), never a minus-like dash, so it cannot
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* // Vantage: import { Delta } from "@squadbase/vantage/components"
|
|
32
32
|
*
|
|
33
33
|
* <Delta value={48_200_000} comparison={{ previous: 42_900_000, vs: "prior-period", grain: "month" }} />
|
|
34
|
-
* <Delta value={0.021} format="percent"
|
|
34
|
+
* <Delta value={0.021} format="percent" betterWhen="down"
|
|
35
35
|
* comparison={{ previous: 0.026, vs: "prior-year", grain: "month" }} />
|
|
36
36
|
*/
|
|
37
37
|
import { forwardRef, type HTMLAttributes } from "react"
|
|
@@ -85,7 +85,9 @@ export interface DeltaProps
|
|
|
85
85
|
value: number | null | undefined
|
|
86
86
|
/** What the value is compared against. Required so the delta always carries its label. */
|
|
87
87
|
comparison: Comparison
|
|
88
|
-
/**
|
|
88
|
+
/** Which direction is good: `"up"` (default) or `"down"` (churn, error rate). Wins over `polarity`. */
|
|
89
|
+
betterWhen?: "up" | "down"
|
|
90
|
+
/** @deprecated Older name of `betterWhen` (`"higher-is-better"` / `"lower-is-better"`); `"neutral"` is only reachable here. `betterWhen` wins if both are given. */
|
|
89
91
|
polarity?: Polarity
|
|
90
92
|
/** Absolute ratio below which the change reads as flat (0.01 = 1%). Default 0.01. */
|
|
91
93
|
neutralBand?: number
|
|
@@ -134,6 +136,7 @@ export const Delta = forwardRef<HTMLSpanElement, DeltaProps>(
|
|
|
134
136
|
{
|
|
135
137
|
value,
|
|
136
138
|
comparison,
|
|
139
|
+
betterWhen,
|
|
137
140
|
polarity,
|
|
138
141
|
neutralBand,
|
|
139
142
|
format,
|
|
@@ -149,7 +152,7 @@ export const Delta = forwardRef<HTMLSpanElement, DeltaProps>(
|
|
|
149
152
|
) => {
|
|
150
153
|
// Nothing to compare against yet (a draft fills props in steps).
|
|
151
154
|
if (!comparison) return null
|
|
152
|
-
const result = computeComparison(value, comparison, { polarity, neutralBand })
|
|
155
|
+
const result = computeComparison(value, comparison, { polarity: betterWhen ? (betterWhen === "down" ? "lower-is-better" : "higher-is-better") : polarity, neutralBand })
|
|
153
156
|
const display = change ?? defaultChangeDisplay(format)
|
|
154
157
|
const formatDigits = typeof format === "object" && format !== null && "digits" in format ? format.digits : undefined
|
|
155
158
|
const changeDigits = display === "points" ? (digits ?? formatDigits) : digits
|
package/registry/ui/kpi-card.tsx
CHANGED
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
* line takes its name from it ("昨日 617"), the same way a `label` on any `vs` does.
|
|
44
44
|
* - **Two or more comparisons sit under the value**, not beside it, so the value
|
|
45
45
|
* keeps its place and its width whatever the card holds.
|
|
46
|
-
* -
|
|
46
|
+
* - **`betterWhen` drives everything.** With `betterWhen="down"` the target
|
|
47
47
|
* is met at or below it, and the record badge marks the lowest value.
|
|
48
48
|
* - **Rates change in points.** With a `percent` format the delta and
|
|
49
49
|
* `{difference}` are in percentage points ("-0.5pt"); `change="relative"` opts
|
|
@@ -147,7 +147,9 @@ export interface KpiCardProps extends Omit<ComponentProps<"div">, "children"> {
|
|
|
147
147
|
* @example provisional / provisional={{ label: "暫定" }}
|
|
148
148
|
*/
|
|
149
149
|
provisional?: boolean | { label?: string }
|
|
150
|
-
/**
|
|
150
|
+
/** Which direction is good: `"up"` (default) or `"down"` (churn, error rate). Wins over `polarity`. */
|
|
151
|
+
betterWhen?: "up" | "down"
|
|
152
|
+
/** @deprecated Older name of `betterWhen` (`"higher-is-better"` / `"lower-is-better"`). `"neutral"` (never colours the change, skips the target and record logic) is only reachable here. `betterWhen` wins if both are given. */
|
|
151
153
|
polarity?: Polarity
|
|
152
154
|
/** `"relative"` ("+12.4%"), `"points"` ("-0.5pt") or `"difference"` ("+¥530万", in `format`). Default: points for a `percent` format, else relative. */
|
|
153
155
|
change?: ChangeDisplay
|
|
@@ -160,7 +162,7 @@ export interface KpiCardProps extends Omit<ComponentProps<"div">, "children"> {
|
|
|
160
162
|
/**
|
|
161
163
|
* Target for the current period. Under the value it adds a 6px bar (actual, the pace
|
|
162
164
|
* projection, a tick at the target) and one line "目標の 49%・このペースなら 98%"
|
|
163
|
-
* (the projection only while `period` is in progress). With `
|
|
165
|
+
* (the projection only while `period` is in progress). With `betterWhen="down"`
|
|
164
166
|
* or a rate target (points) there is no bar: "目標 … まであと …" in the footer.
|
|
165
167
|
*/
|
|
166
168
|
target?: number
|
|
@@ -325,7 +327,8 @@ export function KpiCard({
|
|
|
325
327
|
comparison: comparisonProp,
|
|
326
328
|
comparisons,
|
|
327
329
|
provisional,
|
|
328
|
-
|
|
330
|
+
betterWhen,
|
|
331
|
+
polarity: polarityProp,
|
|
329
332
|
change,
|
|
330
333
|
neutralBand,
|
|
331
334
|
showPrevious = true,
|
|
@@ -352,6 +355,8 @@ export function KpiCard({
|
|
|
352
355
|
const isProvisional = provisional !== undefined && provisional !== false
|
|
353
356
|
const provisionalLabel = typeof provisional === "object" && provisional.label ? provisional.label : "速報"
|
|
354
357
|
const sample = useSampleMark()
|
|
358
|
+
// `betterWhen` is the documented name and wins over the older `polarity`.
|
|
359
|
+
const polarity: Polarity | undefined = betterWhen ? (betterWhen === "down" ? "lower-is-better" : "higher-is-better") : polarityProp
|
|
355
360
|
const neutral = polarity === "neutral"
|
|
356
361
|
const hasPrevious = comparison !== undefined && showPrevious
|
|
357
362
|
// Two or more badges would squeeze the value from the right; they get their own row under it instead.
|
|
@@ -94,6 +94,11 @@ Base UI の `SelectValue` は選択中の **value をそのまま描き**、`Sel
|
|
|
94
94
|
- 表示ルート `/sales/:customerId`
|
|
95
95
|
- リンク `to="/sales/$customerId"` + `params={{ customerId }}` / 取り出し `useParams().customerId`
|
|
96
96
|
|
|
97
|
+
## 日本語アプリは `lang` を指定する
|
|
98
|
+
|
|
99
|
+
ルート `src/_layout.tsx` に `export const lang = "ja"`(リテラル)が無いと `<html lang="en">` のままで、
|
|
100
|
+
スライドの見出しが文節の途中で折れる。内容が日本語なら必ず指定する。
|
|
101
|
+
|
|
97
102
|
## ページ:`definePage` の値はリテラルで書く
|
|
98
103
|
|
|
99
104
|
title/description/navLabel は**ビルド時に静的抽出**される。変数・関数呼び出し・テンプレート補間を
|
|
@@ -58,7 +58,7 @@ export default function JuneDeck() {
|
|
|
58
58
|
## まず block を探す
|
|
59
59
|
|
|
60
60
|
1 枚分の中身は、手で組む前に `@squadbase/vantage/slide` の block(`SlideCover` `SlideExecSummary`
|
|
61
|
-
`SlideKpiHero` `SlideKpiGrid` `SlideChartInsight` `SlideScorecardTable` `SlideNextActions`)に
|
|
61
|
+
`SlideKpiHero` `SlideKpiGrid` `SlideChartInsight` `SlideScorecardTable` `SlideNextActions` `SlideSection` `SlideComparison` `SlideChartFull`)に
|
|
62
62
|
合うものがないか探します。`<Slide>` の**中**に置き、props は `vantage docs <name>`(例:
|
|
63
63
|
`vantage docs slide-cover`)で引く。一覧は `agents/slide.md` が正本です。
|
|
64
64
|
- props は block 間で共通: `value: number | null` + `format`(上書きだけ `display`)、`delta: { value, format? }` + `betterWhen` + `comparisonLabel`、`target: number`、`source` は文字列、`asOf` は日付だけ(「時点」は block が付ける)。
|
|
@@ -72,7 +72,7 @@ export default function JuneDeck() {
|
|
|
72
72
|
## import してよいもの
|
|
73
73
|
|
|
74
74
|
- `@squadbase/vantage/slide` — `SlideDeck` `Slide` `Steps` `Step` `MorphElement` `useSlideNumber`
|
|
75
|
-
`useIsActiveSlide` `SlideCanvas`、1 枚分の block(`SlideCover` ほか上の
|
|
75
|
+
`useIsActiveSlide` `SlideCanvas`、1 枚分の block(`SlideCover` ほか上の 10 個)、手書きの 1 枚に使う部品(`SlideHeading` `SlideSourceLine` `SlideColumns` `SlideColumn` `DeltaText`)と、その型
|
|
76
76
|
- `@squadbase/vantage` — `definePage`
|
|
77
77
|
- `@squadbase/vantage/components` の `EChart` — **チャートを載せるときはこれ**(→「チャート」)
|
|
78
78
|
- `lucide-react` のアイコン
|
package/templates/AGENTS.md
CHANGED
|
@@ -47,6 +47,8 @@ UI キット・開発サーバー・API サーバー・ビルドはすべて Van
|
|
|
47
47
|
- **`definePage` の値はリテラルで書く。** title/description/navLabel はビルド時に静的抽出される
|
|
48
48
|
ため、変数・関数呼び出し・テンプレート補間は使わない。`page` エクスポートはランタイムでは
|
|
49
49
|
読まれない。
|
|
50
|
+
- **アプリの表示言語は、ルート `_layout.tsx` の `export const lang = "ja"`(リテラル)で指定する。**
|
|
51
|
+
`<html lang>` になり、日本語の文節改行とスライドの文言切り替えに効く。未指定は `"en"`。
|
|
50
52
|
|
|
51
53
|
## ディレクトリ構造(ページをどこに置くか)
|
|
52
54
|
|
|
@@ -95,8 +97,8 @@ root/
|
|
|
95
97
|
| `@squadbase/vantage/ui` | shadcn/ui(Base UI バリアント)プリミティブ(`Button`・`Loading`・`ErrorState`・`Empty` ほか) |
|
|
96
98
|
| `@squadbase/vantage/components` | 複合パーツ(`PageShell`・`DashboardCardPreset`・`DataTablePreset`、KPI の `KpiCard`・`Delta`、型付きチャート `LineChart`・`BarChart`・`AreaChart`・`DonutChart`・`BarList` ほか、`EChart`、書式の `formatValue`) |
|
|
97
99
|
| `@squadbase/vantage/table` | `FilePageView`・`TableViewer`(データファイルページの表ビューア) |
|
|
98
|
-
| `@squadbase/vantage/slide` | `SlideDeck`・`Slide`・`Steps`・`Step`・`MorphElement`・`useSlideNumber`・`useIsActiveSlide`、1 枚分の block `SlideCover`・`SlideExecSummary`・`SlideKpiHero`・`SlideKpiGrid`・`SlideChartInsight`・`SlideScorecardTable`・`SlideNextActions`(スライド専用。1920×1080 の固定キャンバス) |
|
|
99
|
-
| `@squadbase/vantage/doc` | `Document`、ドキュメント用の block `DocReportHeader`・`DocExecutiveSummary`・`DocKpiScorecard`・`DocFigure`・`DocDataTable`・`DocCallout`・`DocKeyFindings`・`DocActionItems`(ドキュメント専用。中の素の HTML 要素に見出し・表などのスタイルが当たる) |
|
|
100
|
+
| `@squadbase/vantage/slide` | `SlideDeck`・`Slide`・`Steps`・`Step`・`MorphElement`・`useSlideNumber`・`useIsActiveSlide`、1 枚分の block `SlideCover`・`SlideExecSummary`・`SlideKpiHero`・`SlideKpiGrid`・`SlideChartInsight`・`SlideScorecardTable`・`SlideNextActions`・`SlideSection`・`SlideComparison`・`SlideChartFull`、手書きの 1 枚に使う部品 `SlideHeading`・`SlideSourceLine`・`SlideColumns`・`SlideColumn`・`DeltaText`(スライド専用。1920×1080 の固定キャンバス) |
|
|
101
|
+
| `@squadbase/vantage/doc` | `Document`、ドキュメント用の block `DocReportHeader`・`DocExecutiveSummary`・`DocKpiScorecard`・`DocFigure`・`DocDataTable`・`DocCallout`・`DocKeyFindings`・`DocActionItems`・`DocNotesAndSources`、部品 `DeltaText`(ドキュメント専用。中の素の HTML 要素に見出し・表などのスタイルが当たる) |
|
|
100
102
|
| `@squadbase/vantage/markdown` | `MarkdownRenderer`(Shiki を隔離するための専用サブパス) |
|
|
101
103
|
| `@squadbase/vantage/server` | `ApiContext`・`ApiHandler`・`HttpError`・`isHttpError`・`json`(server/ 側でのみ使う) |
|
|
102
104
|
|
package/templates/agents/doc.md
CHANGED
|
@@ -72,9 +72,12 @@ export default function JuneSummary() {
|
|
|
72
72
|
| `DocCallout` | 補足・示唆・注意・決定の囲み(`kind`: `note` / `insight` / `warning` / `decision`) |
|
|
73
73
|
| `DocKeyFindings` | 主要な発見の箇条 |
|
|
74
74
|
| `DocActionItems` | 次のアクション(担当・期限つき) |
|
|
75
|
+
| `DocNotesAndSources` | 末尾の注記・用語の定義・データソースをまとめた節 |
|
|
75
76
|
|
|
76
77
|
**props の渡し方は block 間で共通。** 数値は `value: number | null` と `format`(整形は block がする。表示を上書きするときだけ `display`。`null` は「—」)。前期比などは `delta: { value, format? }` と `comparisonLabel`(例 `"前年同期比"`)、良し悪しは指標ごとの `betterWhen`(`"up"` が既定、コストなど下がるほど良いものは `"down"`。向きは block が符号から出す)。目標は `target: number`。出典は `source`、時点は `asOf` に**日付だけ**(例 `"2026-09-30"`。「データ時点」は block が付けるので「時点」を書かない)。時系列は、単一系列なら `{ x, y }[]`(`trend`)。
|
|
77
78
|
|
|
79
|
+
本文の中で増減を書くときは `DeltaText`(部品。「▲ +3.2%」の形で、文字サイズは持たず置かれた行に従う。`value` `format` `betterWhen` `comparisonLabel`)を使う。節ではなく文の中に置く。
|
|
80
|
+
|
|
78
81
|
block が合わない**1 節だけ**を素の HTML 要素で手で書く。import 元は `@squadbase/vantage/doc` のままで、
|
|
79
82
|
block のための新しい import 元はありません。本文は文字列か ReactNode で渡し、Markdown 文字列は渡さない。
|
|
80
83
|
|
|
@@ -70,13 +70,28 @@ pptx が壊れる条件、説明と図表のデータ時点の揃え方)。
|
|
|
70
70
|
| `SlideChartInsight` | チャート 1 つと、そこから読み取れること |
|
|
71
71
|
| `SlideScorecardTable` | 指標と目標・達成度を表にしたスコアカード |
|
|
72
72
|
| `SlideNextActions` | 次のアクション(担当・期限つき) |
|
|
73
|
+
| `SlideSection` | 章の扉(`items` を渡すと番号付きのアジェンダ。`current` の章を強調) |
|
|
74
|
+
| `SlideComparison` | A vs B・前後の 2〜3 列比較(各列に見出し・主要数値・箇条) |
|
|
75
|
+
| `SlideChartFull` | チャートを全幅に置き、注記を 1 行添える |
|
|
76
|
+
|
|
77
|
+
**手書きの 1 枚には部品を使う。** block が合わない 1 枚でも、見出し・出典行・列割りは自前で組まず、
|
|
78
|
+
次の部品を使う(どれも `@squadbase/vantage/slide`。1 枚にはならないので `<Slide>` の中に並べる)。
|
|
79
|
+
|
|
80
|
+
| 部品 | 用途 |
|
|
81
|
+
| --- | --- |
|
|
82
|
+
| `SlideHeading` | 見出し(`title`、任意で `eyebrow`)。1 枚の先頭に置く |
|
|
83
|
+
| `SlideSourceLine` | 足元の出典・時点(`source` `asOf`)。`page={{ current, total }}` を渡すとページ番号も出す |
|
|
84
|
+
| `SlideColumns` / `SlideColumn` | 12 列の割り付け(`<SlideColumn span={8}>` と `span={4}` など)。`align` は `start` / `center` / `stretch` |
|
|
85
|
+
| `DeltaText` | 増減の文字(「▲ +3.2%」)。`value` `format` `betterWhen` `comparisonLabel` |
|
|
86
|
+
|
|
87
|
+
`SlideSection` は `Steps` を持たない。順に見せたいときは呼び出し側で `<Steps>` で包む。
|
|
73
88
|
|
|
74
89
|
```tsx
|
|
75
90
|
import { Slide, SlideDeck, SlideCover, SlideExecSummary } from "@squadbase/vantage/slide"
|
|
76
91
|
// <SlideDeck><Slide><SlideCover … /></Slide><Slide><SlideExecSummary … /></Slide></SlideDeck>
|
|
77
92
|
```
|
|
78
93
|
|
|
79
|
-
**props の渡し方は block 間で共通。** 数値は `value: number | null` と `format`(整形は block がする。表示を上書きするときだけ `display`。`null` は「—」)。前期比などは `delta: { value, format? }` と `comparisonLabel`(例 `"前年同期比"`)、良し悪しは指標ごとの `betterWhen`(`"up"` が既定、コストなど下がるほど良いものは `"down"`。向きは block が符号から出す)。目標は `target: number`。出典は `source`(文字列)、時点は `asOf` に**日付だけ**(例 `"2026-09-30"`。「時点」は block が付けるので書かない)。時系列は、単一系列なら `{ x, y }[]`(kpi-hero
|
|
94
|
+
**props の渡し方は block 間で共通。** 数値は `value: number | null` と `format`(整形は block がする。表示を上書きするときだけ `display`。`null` は「—」)。前期比などは `delta: { value, format? }` と `comparisonLabel`(例 `"前年同期比"`)、良し悪しは指標ごとの `betterWhen`(`"up"` が既定、コストなど下がるほど良いものは `"down"`。向きは block が符号から出す)。目標は `target: number`。出典は `source`(文字列)、時点は `asOf` に**日付だけ**(例 `"2026-09-30"`。「時点」は block が付けるので書かない)。時系列は、単一系列なら `{ x, y }[]`(kpi-hero の `trend`。scorecard に `trend` は無い)、複数系列なら `{ categories, series }`(chart-insight の `chart`)。
|
|
80
95
|
|
|
81
96
|
block が合わない**1 枚だけ**を手で書く(デッキ全体を手書きに切り替えない)。import 元は
|
|
82
97
|
`@squadbase/vantage/slide` のままで、block のための新しい import 元はありません。
|
package/theme.css
CHANGED
|
@@ -366,11 +366,14 @@
|
|
|
366
366
|
|
|
367
367
|
:root {
|
|
368
368
|
/* Slides (@squadbase/vantage/slide).
|
|
369
|
-
These are aliases, not values.
|
|
369
|
+
These are aliases, not values. A var() in a custom property is resolved
|
|
370
|
+
on the element that declares it and inherited as that result, so
|
|
370
371
|
pointing them at the tokens above is enough for a deck to follow `.dark`
|
|
371
|
-
and `[data-theme="dark"]` on
|
|
372
|
-
below would create two places to keep in step for no gain.
|
|
373
|
-
|
|
372
|
+
and `[data-theme="dark"]` set on :root — restating them in the dark block
|
|
373
|
+
below would create two places to keep in step for no gain. Tokens
|
|
374
|
+
overridden on a descendant (a scoped theme) do not reach them: re-declare
|
|
375
|
+
the aliases there. A deck overrides any of them inline through
|
|
376
|
+
`<SlideDeck theme>`. */
|
|
374
377
|
--slide-bg: var(--background);
|
|
375
378
|
--slide-fg: var(--foreground);
|
|
376
379
|
--slide-accent: var(--primary);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/diagnostics.ts"],"names":[],"mappings":"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","file":"chunk-7DZGJQ3P.js","sourcesContent":["import fs from \"node:fs\"\nimport path from \"node:path\"\nimport {\n API_METHODS,\n FILE_PAGE_EXTENSIONS,\n FILE_PAGE_MAX_BYTES,\n FORBIDDEN_FILES,\n PUBLIC_ENV_PREFIX,\n ROUTED_EXTENSIONS,\n SRC_DIR,\n SUSPICIOUS_ROUTE_DIRS,\n} from \"./constants\"\nimport { isHomePagePath, isPlaceholderHomePage } from \"./placeholder\"\nimport { timed } from \"./profile\"\nimport { detectApiMethods } from \"./route-spec\"\nimport { maskSource, stripComments } from \"./source\"\nimport type { Diagnostic, RouteModel } from \"./types\"\n\n/** Vite/env identifiers that are always allowed on `import.meta.env`. */\nconst ALLOWED_ENV_KEYS = new Set([\"MODE\", \"DEV\", \"PROD\", \"SSR\", \"BASE_URL\", \"PUBLIC_\"])\n\n/** Whether a module has a default export (direct, aliased, or re-exported). */\nfunction hasDefaultExport(src: string): boolean {\n return (\n /\\bexport\\s+default\\b/.test(src) ||\n /\\bexport\\s*\\{[^}]*\\bas\\s+default\\b[^}]*\\}/.test(src) ||\n /\\bexport\\s*\\{[^}]*\\bdefault\\b[^}]*\\}\\s*from\\b/.test(src)\n )\n}\n\n/** Collect client-side source files (everything outside server/ and generated dirs). */\nfunction collectClientFiles(root: string): string[] {\n const out: string[] = []\n const skipDirs = new Set([\"node_modules\", \".vantage\", \"dist\", \"server\", \"public\"])\n const walk = (dir: string) => {\n let entries: fs.Dirent[]\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true })\n } catch {\n return\n }\n for (const entry of entries) {\n if (entry.name.startsWith(\".\")) continue\n const abs = path.join(dir, entry.name)\n if (entry.isDirectory()) {\n if (skipDirs.has(entry.name)) continue\n walk(abs)\n } else if (/\\.(tsx?|jsx?)$/.test(entry.name)) {\n out.push(abs)\n }\n }\n }\n walk(root)\n return out\n}\n\n/**\n * Per-run scratch space. Every checker reads the filesystem through this, so a\n * `check` walks the project once and reads (and `stripComments`) each source\n * file at most once — page modules used to be read three times over, and the\n * client tree used to be walked twice.\n */\ninterface Ctx {\n root: string\n model: RouteModel\n /** Raw file text for an absolute path; null when unreadable. */\n raw(abs: string): string | null\n /** Comment-stripped source for an absolute path; null when unreadable. */\n source(abs: string): string | null\n /**\n * Byte size of a file; null when unreadable. Separate from `source` because\n * `source` decodes as UTF-8 and strips comments — fine for a page module,\n * ruinous for a few MB of spreadsheet.\n */\n size(abs: string): number | null\n /** Client-side source files, walked lazily and only once. */\n clientFiles(): string[]\n /** `readdir` of the project root, read lazily and only once. */\n rootEntries(): string[]\n}\n\nfunction createCtx(root: string, model: RouteModel): Ctx {\n const raws = new Map<string, string | null>()\n const sources = new Map<string, string | null>()\n const sizes = new Map<string, number | null>()\n let clientFiles: string[] | null = null\n let rootEntries: string[] | null = null\n\n const raw = (abs: string): string | null => {\n const hit = raws.get(abs)\n if (hit !== undefined) return hit\n let text: string | null\n try {\n text = fs.readFileSync(abs, \"utf8\")\n } catch {\n text = null\n }\n raws.set(abs, text)\n return text\n }\n\n return {\n root,\n model,\n raw,\n source(abs) {\n const hit = sources.get(abs)\n if (hit !== undefined) return hit\n const text = raw(abs)\n const src = text === null ? null : stripComments(text)\n sources.set(abs, src)\n return src\n },\n size(abs) {\n const hit = sizes.get(abs)\n if (hit !== undefined) return hit\n let bytes: number | null\n try {\n bytes = fs.statSync(abs).size\n } catch {\n bytes = null\n }\n sizes.set(abs, bytes)\n return bytes\n },\n clientFiles() {\n return (clientFiles ??= collectClientFiles(root))\n },\n rootEntries() {\n if (rootEntries) return rootEntries\n try {\n rootEntries = fs.readdirSync(root)\n } catch {\n rootEntries = []\n }\n return rootEntries\n },\n }\n}\n\nfunction rel(root: string, abs: string): string {\n return path.relative(root, abs).split(path.sep).join(\"/\")\n}\n\n/** Detect forbidden config files at the project root. */\nfunction checkForbiddenFiles(ctx: Ctx): Diagnostic[] {\n const diags: Diagnostic[] = []\n for (const name of ctx.rootEntries()) {\n for (const rule of FORBIDDEN_FILES) {\n if (rule.pattern.test(name)) {\n diags.push({\n code: \"FORBIDDEN_FILE\",\n severity: \"error\",\n message: `${rule.label} file \"${name}\" is not allowed in a Vantage project.`,\n files: [name],\n fix: rule.remedy,\n })\n }\n }\n }\n return diags\n}\n\n/**\n * Detect a project split between `src/` and the project root. Once `src/`\n * exists it is the only page-scan root, so anything left behind at the root is\n * silently dead — and `server/` moved *into* `src/` is silently unserved.\n */\nfunction checkSrcLayout(ctx: Ctx): Diagnostic[] {\n const { root, model } = ctx\n if (model.pageDir === \"\") return []\n const diags: Diagnostic[] = []\n\n const entries = ctx.rootEntries()\n // Lowercased before matching, as scan does: `Sales.CSV` at the root is a\n // stray page too, and saying so is the whole point of this check.\n const strayPages = entries.filter((name) =>\n ROUTED_EXTENSIONS.some((ext) => name.toLowerCase().endsWith(ext)),\n )\n if (strayPages.length > 0) {\n diags.push({\n code: \"SRC_DIR_SPLIT\",\n severity: \"error\",\n message: `${strayPages.length} page module(s) sit at the project root, but ${SRC_DIR}/ exists so pages are only scanned from ${SRC_DIR}/.`,\n files: strayPages.sort(),\n fix: `Move them into ${SRC_DIR}/, or delete ${SRC_DIR}/ to keep a root-level layout.`,\n })\n }\n\n if (entries.includes(\"styles.css\")) {\n diags.push({\n code: \"SRC_DIR_SPLIT\",\n severity: \"error\",\n message: `styles.css sits at the project root, but ${SRC_DIR}/ exists so the global stylesheet is read from ${SRC_DIR}/styles.css.`,\n files: [\"styles.css\"],\n fix: `Move it to ${SRC_DIR}/styles.css.`,\n })\n }\n\n if (fs.existsSync(path.join(root, SRC_DIR, \"server\"))) {\n diags.push({\n code: \"SRC_DIR_SPLIT\",\n severity: \"error\",\n message: `${SRC_DIR}/server/ is never scanned: server code lives at the project root, outside the client source tree.`,\n files: [`${SRC_DIR}/server`],\n fix: `Move it to server/ at the project root.`,\n })\n }\n\n return diags\n}\n\n/** Flag routes generated from directories that are almost certainly scaffolding. */\nfunction checkSuspiciousRouteDirs(model: RouteModel): Diagnostic[] {\n const byDir = new Map<string, { files: string[]; routes: string[] }>()\n for (const page of model.pages) {\n const top = page.dir.split(\"/\")[0] ?? \"\"\n if (top === \"\" || !SUSPICIOUS_ROUTE_DIRS.has(top)) continue\n const hit = byDir.get(top) ?? { files: [], routes: [] }\n hit.files.push(page.file)\n hit.routes.push(page.routePath)\n byDir.set(top, hit)\n }\n\n const diags: Diagnostic[] = []\n for (const [dir, hit] of byDir) {\n diags.push({\n code: \"SUSPICIOUS_ROUTE_DIR\",\n severity: \"warning\",\n message: `${dir}/ is part of the URL: ${hit.routes.slice(0, 3).join(\", \")}${\n hit.routes.length > 3 ? \", …\" : \"\"\n }`,\n files: hit.files,\n fix: `Directory names become route segments. Non-page modules belong in components/, hooks/ or lib/ (never scanned); if ${dir}/ really is meant to be a URL segment, ignore this warning.`,\n })\n }\n return diags\n}\n\n/** Detect two page files resolving to the same route path. */\nfunction checkRouteConflicts(ctx: Ctx): Diagnostic[] {\n const byPath = new Map<string, string[]>()\n for (const page of ctx.model.pages) {\n const list = byPath.get(page.routePath) ?? []\n list.push(page.file)\n byPath.set(page.routePath, list)\n }\n const diags: Diagnostic[] = []\n for (const [routePath, files] of byPath) {\n if (files.length <= 1) continue\n\n // Special case: if route is \"/\" and one of the files is the untouched\n // template placeholder home page, exclude it from the conflict.\n // This allows new artifacts (e.g. index.slide.tsx) to claim \"/\" when\n // the placeholder has not been edited.\n if (routePath === \"/\") {\n const nonPlaceholders = files.filter((file) => {\n if (!isHomePagePath(file)) return true\n const abs = path.join(ctx.root, file)\n const content = ctx.raw(abs)\n return content === null || !isPlaceholderHomePage(content)\n })\n\n // If only one non-placeholder file remains, no conflict.\n if (nonPlaceholders.length <= 1) continue\n\n // If multiple non-placeholder files, report conflict with only those.\n if (nonPlaceholders.length < files.length) {\n diags.push({\n code: \"ROUTE_CONFLICT\",\n severity: \"error\",\n message: `${nonPlaceholders.length} files resolve to ${routePath}`,\n files: nonPlaceholders,\n fix: \"Rename or remove one route file so each route is unique. A page-kind suffix and a data-file extension both drop out of the URL, so `june.slide.tsx`, `june.csv` and `june.tsx` all resolve to /june.\",\n })\n continue\n }\n }\n\n diags.push({\n code: \"ROUTE_CONFLICT\",\n severity: \"error\",\n message: `${files.length} files resolve to ${routePath}`,\n files,\n fix: \"Rename or remove one route file so each route is unique. A page-kind suffix and a data-file extension both drop out of the URL, so `june.slide.tsx`, `june.csv` and `june.tsx` all resolve to /june.\",\n })\n }\n return diags\n}\n\n/**\n * Speak up about files the scan walked past. Neither case reaches the router,\n * so nothing else in the toolchain has an opportunity to mention them: the\n * page simply never appears in `routes`, in the nav, or at a URL. Without this\n * the only symptom is absence.\n */\nfunction checkUnroutedFiles(ctx: Ctx): Diagnostic[] {\n const diags: Diagnostic[] = []\n for (const entry of ctx.model.unrouted) {\n if (entry.reason === \"extension\") {\n diags.push({\n code: \"UNSUPPORTED_PAGE_FILE\",\n severity: \"warning\",\n message: `\"${entry.file}\" is not a page: Vantage has no viewer for \"${path.posix.extname(\n entry.file,\n )}\".`,\n files: [entry.file],\n fix: `Only ${FILE_PAGE_EXTENSIONS.join(\" and \")} files become pages. Convert it, or render it from a .tsx page with <TableViewer> from @squadbase/vantage/table.`,\n })\n continue\n }\n diags.push({\n code: \"INVALID_PAGE_VARIANT\",\n severity: \"error\",\n message: `\"${entry.file}\" carries the \"${entry.variant}\" page kind but is not a page module, so it is not routed.`,\n files: [entry.file],\n fix: `A page kind only applies to a page module that becomes a route: name it <name>.${entry.variant}.tsx. Layouts, _404/_error and underscore-prefixed modules cannot carry one.`,\n })\n }\n return diags\n}\n\n/**\n * Data-file pages are parsed in the browser, so an oversized file does not\n * fail the build — it renders an error on its own page. A warning, not an\n * error, keeps one large export from blocking a deploy of everything else.\n */\nfunction checkFilePageSize(ctx: Ctx): Diagnostic[] {\n const diags: Diagnostic[] = []\n for (const page of ctx.model.pages) {\n if (page.kind !== \"file\") continue\n const size = ctx.size(page.absFile)\n if (size === null || size <= FILE_PAGE_MAX_BYTES) continue\n diags.push({\n code: \"FILE_PAGE_TOO_LARGE\",\n severity: \"warning\",\n message: `${page.file} is ${(size / 1024 / 1024).toFixed(1)} MB, so ${page.routePath} will show an error instead of a table.`,\n files: [page.file],\n fix: `Data-file pages are parsed in the browser and capped at ${\n FILE_PAGE_MAX_BYTES / 1024 / 1024\n } MB. Serve larger data from server/api/ and render it with DataTable.`,\n })\n }\n return diags\n}\n\n/** Ensure every page module has a default export. */\nfunction checkPageExports(ctx: Ctx): Diagnostic[] {\n const { root, model } = ctx\n const diags: Diagnostic[] = []\n const pageFiles = [\n // Data-file pages have no default export to look for — their component is\n // the generated wrapper, not the `.csv` itself.\n ...model.pages.filter((p) => p.kind === \"module\").map((p) => p.absFile),\n ...model.layouts.map((l) => l.absFile),\n model.notFound && path.join(root, model.notFound),\n model.error && path.join(root, model.error),\n ].filter((x): x is string => Boolean(x))\n\n for (const abs of pageFiles) {\n const src = ctx.source(abs)\n if (src === null) continue\n if (!hasDefaultExport(src)) {\n diags.push({\n code: \"MISSING_DEFAULT_EXPORT\",\n severity: \"error\",\n message: `Page module has no default export.`,\n files: [rel(root, abs)],\n fix: \"A page/layout must `export default` a React component.\",\n })\n }\n }\n return diags\n}\n\n/** Ensure every API module exports at least one valid HTTP method handler. */\nfunction checkApiExports(ctx: Ctx): Diagnostic[] {\n const diags: Diagnostic[] = []\n for (const api of ctx.model.apis) {\n const src = ctx.source(api.absFile)\n if (src === null) continue\n const exported = detectApiMethods(src)\n if (exported.length === 0) {\n // Common mistake: lowercase method names.\n const lower = [\"get\", \"post\", \"put\", \"patch\", \"delete\", \"options\"].filter((m) =>\n new RegExp(`export\\\\s+(async\\\\s+)?function\\\\s+${m}\\\\b`).test(src),\n )\n diags.push({\n code: \"INVALID_API_EXPORT\",\n severity: \"error\",\n message: `API module exports no HTTP method handler${\n lower.length ? ` (found lowercase \"${lower.join(\", \")}\")` : \"\"\n }.`,\n files: [api.file],\n fix: `Export one of: ${API_METHODS.join(\", \")} (uppercase).`,\n })\n }\n }\n return diags\n}\n\n/**\n * The two source-level client checks — the server-import boundary and non-public\n * env reads — in a single pass over the client tree. They used to walk and read\n * the whole tree once each; the diagnostics are still emitted grouped by code so\n * the reported order is unchanged.\n */\nfunction checkClientSources(ctx: Ctx): { boundary: Diagnostic[]; env: Diagnostic[] } {\n const { root } = ctx\n const boundary: Diagnostic[] = []\n const env: Diagnostic[] = []\n const importRe = /(?:import|export)[^'\"]*?from\\s+[\"']([^\"']+)[\"']|import\\s*\\(\\s*[\"']([^\"']+)[\"']\\s*\\)/g\n const envRe = /import\\.meta\\.env\\.([A-Za-z_][A-Za-z0-9_]*)/g\n\n for (const abs of ctx.clientFiles()) {\n const src = ctx.source(abs)\n if (src === null) continue\n\n let m: RegExpExecArray | null\n importRe.lastIndex = 0\n while ((m = importRe.exec(src))) {\n const spec = m[1] ?? m[2]\n if (!spec) continue\n const isServer =\n spec === \"server\" ||\n spec.startsWith(\"server/\") ||\n /(^|\\/)\\.\\.?\\/server(\\/|$)/.test(spec) ||\n /\\/server\\//.test(spec)\n if (isServer) {\n boundary.push({\n code: \"CLIENT_IMPORTS_SERVER\",\n severity: \"error\",\n message: `Client module imports from server/ (\"${spec}\").`,\n files: [rel(root, abs)],\n fix: \"server/ code must never be imported by the client. Move shared code to lib/.\",\n })\n }\n }\n\n envRe.lastIndex = 0\n const bad = new Set<string>()\n while ((m = envRe.exec(src))) {\n const key = m[1]!\n if (ALLOWED_ENV_KEYS.has(key)) continue\n if (key.startsWith(PUBLIC_ENV_PREFIX)) continue\n bad.add(key)\n }\n for (const key of bad) {\n env.push({\n code: \"PUBLIC_ENV_MISUSE\",\n severity: \"warning\",\n message: `Client reads non-public env \"import.meta.env.${key}\".`,\n files: [rel(root, abs)],\n fix: `Only ${PUBLIC_ENV_PREFIX}* vars reach the client. Read secrets via ApiContext.env in server/.`,\n })\n }\n }\n return { boundary, env }\n}\n\n// ── hard-coded colors ────────────────────────────────────────────────────────\n\n/** Tailwind utilities that take a color. */\nconst COLOR_PREFIXES =\n \"text|bg|border|ring|fill|stroke|from|via|to|outline|decoration|divide|accent|caret|shadow|placeholder\"\nconst PALETTES =\n \"slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose\"\nconst COLOR_FNS = \"rgba?|hsla?|oklch|oklab|lab|lch|color\"\n\n/** `dark:hover:bg-blue-500/50` — variants, prefix, palette, opacity. */\nconst PALETTE_CLASS_RE = new RegExp(\n `(?<![\\\\w-])((?:[\\\\w-]+:)*)(${COLOR_PREFIXES})-(${PALETTES})-(50|[1-9]00|950)(\\\\/(?:\\\\d+|\\\\[[^\\\\]\\\\s]+\\\\]))?(?![\\\\w-])`,\n \"g\",\n)\n/** `bg-[#0f172a]`, `text-[rgb(1,2,3)]`, `shadow-[0_0_4px_rgba(…)]`. */\nconst ARBITRARY_CLASS_RE = new RegExp(\n `(?<![\\\\w-])((?:[\\\\w-]+:)*)(${COLOR_PREFIXES})-\\\\[([^\\\\]\\\\s\"'\\`]+)\\\\]`,\n \"g\",\n)\nconst COLOR_FN_RE = new RegExp(\n `(?<![\\\\w-])(?:${COLOR_FNS})\\\\((?:[^()]|\\\\([^()]*\\\\))*\\\\)`,\n \"g\",\n)\nconst HEX_RE = /(?<![\\w&#/-])#([0-9a-fA-F]{3,8})(?![\\w-])/g\nconst HEX_LITERAL_RE = /(?<![\\w&])#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{3,4})(?![\\w-])/\n/** `href=\"#abc\"`, `to=\"#add\"` — an anchor, not a color. */\nconst ANCHOR_BEFORE_RE = /\\b(?:href|to|id|hash)\\s*[=:]\\s*\\{?\\s*[\"'`]$/\n\nconst NEUTRAL_PALETTES = new Set([\"slate\", \"gray\", \"zinc\", \"neutral\", \"stone\"])\nconst DANGER_PALETTES = new Set([\"red\", \"rose\"])\n/** Nearest chart token per hue family — the `--chart-1..5` order in theme.css. */\nconst CHART_BY_PALETTE: Record<string, string> = {\n blue: \"chart-1\",\n indigo: \"chart-1\",\n sky: \"chart-1\",\n cyan: \"chart-1\",\n green: \"chart-2\",\n emerald: \"chart-2\",\n teal: \"chart-2\",\n lime: \"chart-2\",\n orange: \"chart-3\",\n amber: \"chart-3\",\n yellow: \"chart-3\",\n violet: \"chart-4\",\n purple: \"chart-4\",\n fuchsia: \"chart-4\",\n pink: \"chart-5\",\n}\nconst MAX_COLOR_HITS = 20\n\n/** Semantic-token candidates for a Tailwind palette class. */\nfunction suggestPaletteClass(variants: string, prefix: string, palette: string, opacity: string): string[] {\n const t = (token: string) => `${variants}${prefix}-${token}${opacity}`\n if (DANGER_PALETTES.has(palette)) return [t(\"destructive\")]\n if (NEUTRAL_PALETTES.has(palette)) {\n if (prefix === \"text\") return [t(\"foreground\"), t(\"muted-foreground\")]\n if (prefix === \"bg\") return [t(\"muted\"), t(\"card\")]\n if (prefix === \"border\" || prefix === \"divide\" || prefix === \"outline\") return [t(\"border\")]\n if (prefix === \"ring\") return [t(\"ring\")]\n return [t(\"muted-foreground\")]\n }\n const chart = CHART_BY_PALETTE[palette] ?? \"chart-1\"\n return chart === \"chart-1\" ? [t(\"primary\"), t(chart)] : [t(chart), t(\"primary\")]\n}\n\n/** Whether a Tailwind arbitrary value carries a literal color. */\nfunction isColorValue(value: string): boolean {\n if (value.includes(\"var(\")) return false\n return HEX_LITERAL_RE.test(value) || new RegExp(`(?<![\\\\w-])(?:${COLOR_FNS})\\\\(`).test(value)\n}\n\ninterface ColorHit {\n line: number\n text: string\n suggest: string[]\n}\n\n/** Every hard-coded color in one module, with raw-file line numbers. */\nfunction findColorHits(raw: string): ColorHit[] {\n const rawLines = raw.split(\"\\n\")\n if (rawLines.slice(0, 5).some((l) => l.includes(\"vantage-ignore-color-file\"))) return []\n // A marker exempts its own line (trailing comment) and the next (comment above).\n const ignored = new Set<number>()\n rawLines.forEach((l, i) => {\n if (l.includes(\"vantage-ignore-color\")) ignored.add(i + 1).add(i + 2)\n })\n\n const { code, inString } = maskSource(raw)\n const lineStarts = [0]\n for (let i = 0; i < raw.length; i++) if (raw[i] === \"\\n\") lineStarts.push(i + 1)\n const lineAt = (offset: number) => {\n let lo = 0\n let hi = lineStarts.length - 1\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1\n if (lineStarts[mid]! <= offset) lo = mid\n else hi = mid - 1\n }\n return lo + 1\n }\n\n const hits: ColorHit[] = []\n const covered: [number, number][] = []\n const isCovered = (at: number) => covered.some(([s, e]) => at >= s && at < e)\n const push = (at: number, end: number, text: string, suggest: string[]) => {\n if (!inString[at] || isCovered(at)) return\n covered.push([at, end])\n const line = lineAt(at)\n if (ignored.has(line)) return\n hits.push({ line, text: text.length > 48 ? `${text.slice(0, 47)}…` : text, suggest })\n }\n\n let m: RegExpExecArray | null\n ARBITRARY_CLASS_RE.lastIndex = 0\n while ((m = ARBITRARY_CLASS_RE.exec(code))) {\n if (!isColorValue(m[3]!)) continue\n const token = (m[2] === \"shadow\" ? \"\" : m[2]!) || \"bg\"\n push(m.index, m.index + m[0].length, m[0], [\n `${m[1]}${token}-primary`,\n `${m[1]}${token}-chart-1`,\n ])\n }\n PALETTE_CLASS_RE.lastIndex = 0\n while ((m = PALETTE_CLASS_RE.exec(code))) {\n push(\n m.index,\n m.index + m[0].length,\n m[0],\n suggestPaletteClass(m[1]!, m[2]!, m[3]!, m[5] ?? \"\"),\n )\n }\n const literalSuggest = [\"var(--primary)\", \"var(--chart-1..5)\", \"var(--muted-foreground)\"]\n COLOR_FN_RE.lastIndex = 0\n while ((m = COLOR_FN_RE.exec(code))) {\n if (m[0].includes(\"var(\")) continue\n push(m.index, m.index + m[0].length, m[0], literalSuggest)\n }\n HEX_RE.lastIndex = 0\n while ((m = HEX_RE.exec(code))) {\n const hex = m[1]!\n if (![3, 4, 6, 8].includes(hex.length)) continue\n const before = code.slice(Math.max(0, m.index - 24), m.index)\n if (ANCHOR_BEFORE_RE.test(before)) continue\n // `\"Order #123\"` is a number, not a color, unless it opens a literal.\n if (hex.length <= 4 && /^\\d+$/.test(hex) && !/[\"'`[]$/.test(before)) continue\n push(m.index, m.index + m[0].length, m[0], literalSuggest)\n }\n return hits.sort((a, b) => a.line - b.line)\n}\n\n/**\n * Colors belong to the theme: `theme.css` / `styles.css` define the tokens and\n * DESIGN.md names them, so a literal in a component silently opts out of light/\n * dark, `styles.css` overrides and chart presets. Only `.ts`/`.tsx` is scanned —\n * `.css` is where the values are supposed to live. Warning, never error: a map\n * fill or a status color can legitimately carry data meaning.\n */\nfunction checkHardcodedColors(ctx: Ctx): Diagnostic[] {\n const diags: Diagnostic[] = []\n for (const abs of ctx.clientFiles()) {\n if (!/\\.tsx?$/.test(abs) || abs.endsWith(\".d.ts\")) continue\n const raw = ctx.raw(abs)\n if (raw === null) continue\n const hits = findColorHits(raw)\n if (hits.length === 0) continue\n const file = rel(ctx.root, abs)\n const shown = hits.slice(0, MAX_COLOR_HITS)\n const more = hits.length - shown.length\n const lines = shown.map((h) => `L${h.line}: ${h.text} → suggest: ${h.suggest.join(\" / \")}`)\n if (more > 0) lines.push(`+${more} more`)\n diags.push({\n code: \"HARDCODED_COLOR\",\n severity: \"warning\",\n message: `${file} hard-codes ${hits.length} color(s):\\n ${lines.join(\"\\n \")}`,\n files: [...new Set(shown.map((h) => `${file}:${h.line}`))].concat(more > 0 ? [`+${more} more`] : []),\n fix: \"Use theme tokens instead (bg-primary, text-muted-foreground, var(--chart-N), … as defined in styles.css and named in DESIGN.md). If a color carries data meaning (e.g. map fills), put `// vantage-ignore-color` (`{/* vantage-ignore-color */}` inside JSX) on that line or the line above; `vantage-ignore-color-file` within the first 5 lines exempts the whole file.\",\n })\n }\n return diags\n}\n\nconst DESIGN_MD_MAX_LINES = 150\n\n/** DESIGN.md is a map of token names, not a second copy of styles.css. */\nfunction checkDesignDoc(ctx: Ctx): Diagnostic[] {\n if (!ctx.rootEntries().includes(\"DESIGN.md\")) return []\n const raw = ctx.raw(path.join(ctx.root, \"DESIGN.md\"))\n if (raw === null) return []\n const lines = raw.replace(/\\n$/, \"\").split(\"\\n\").length\n if (lines <= DESIGN_MD_MAX_LINES) return []\n return [\n {\n code: \"DESIGN_MD_TOO_LONG\",\n severity: \"warning\",\n message: `DESIGN.md is ${lines} lines long (aim for ${DESIGN_MD_MAX_LINES} or fewer).`,\n files: [\"DESIGN.md\"],\n fix: \"Keep concrete values (colors, spacing, …) in styles.css and refer to them by token name (--primary, --chart-1, …) in DESIGN.md to keep it short.\",\n },\n ]\n}\n\n/** Run all static diagnostics for a project. */\nexport function runDiagnostics(root: string, model: RouteModel): Diagnostic[] {\n return timed(\"diagnostics\", () => {\n const ctx = createCtx(root, model)\n const client = checkClientSources(ctx)\n return [\n ...checkForbiddenFiles(ctx),\n ...checkSrcLayout(ctx),\n ...checkSuspiciousRouteDirs(model),\n ...checkRouteConflicts(ctx),\n ...checkUnroutedFiles(ctx),\n ...checkFilePageSize(ctx),\n ...checkPageExports(ctx),\n ...checkApiExports(ctx),\n ...client.boundary,\n ...client.env,\n ...checkHardcodedColors(ctx),\n ...checkDesignDoc(ctx),\n ]\n })\n}\n"]}
|