paneltir 0.6.0
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/CLAUDE_MD_SNIPPET.md +66 -0
- package/INSTALL.md +261 -0
- package/LICENSE +90 -0
- package/README.md +404 -0
- package/bin/paneltir.mjs +331 -0
- package/dist/index.d.ts +1489 -0
- package/dist/index.js +3754 -0
- package/dist/style.css +1973 -0
- package/dist/style.d.ts +2 -0
- package/fingerprint.json +7 -0
- package/package.json +94 -0
- package/renovate.json.example +13 -0
- package/template/.claude/skills/panel/SKILL.md +155 -0
- package/template/api/login.ts +63 -0
- package/template/api/logout.ts +31 -0
- package/template/api/panel-state.ts +317 -0
- package/template/lib/session.ts +170 -0
- package/template/middleware.ts +175 -0
- package/template/src/data/panel-state.json +83 -0
package/README.md
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
# Paneltir
|
|
2
|
+
|
|
3
|
+
**Paneltir** is the DFKlabs UI kit for administration panels. It owns
|
|
4
|
+
**structure and behaviour** โ page frame, a board with touch-ready drag and
|
|
5
|
+
drop, detail sheets, theming โ and never colour or brand. Each project hands
|
|
6
|
+
it a palette and that is the whole customisation.
|
|
7
|
+
|
|
8
|
+
> โ ๏ธ **Proprietary software.** This repository is private and is not open
|
|
9
|
+
> source. Access is granted only to authorised projects; copying,
|
|
10
|
+
> redistribution or modification without written permission is prohibited.
|
|
11
|
+
> See [`LICENSE`](./LICENSE).
|
|
12
|
+
|
|
13
|
+
๐ **Site:** [paneltir.dfklabs.com](https://paneltir.dfklabs.com) โ
|
|
14
|
+
[live demo](https://paneltir.dfklabs.com/demo) ยท
|
|
15
|
+
[project panel](https://paneltir.dfklabs.com/admin) (password protected)
|
|
16
|
+
|
|
17
|
+
## Repository contents
|
|
18
|
+
|
|
19
|
+
| Path | What it is |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| `src/` | The kit: React + TypeScript components with a stylesheet per component. |
|
|
22
|
+
| `src/themes/presets.ts` | Base palettes: `cyberpunkTheme` (Night City), `midnightTheme`, `oldMoneyTheme`. |
|
|
23
|
+
| `site/` | The website: landing, live demo, the project's own panel and the legal pages. |
|
|
24
|
+
| `site/src/data/panel-state.json` | The board behind `/admin` โ panel content as version-controlled data. |
|
|
25
|
+
| `middleware.ts` | Vercel edge middleware that password-protects `/admin` and the panel API. |
|
|
26
|
+
| `api/panel-state.ts` | Serverless function that commits the board back to this repository. |
|
|
27
|
+
| `.claude/skills/todo/SKILL.md` | `/todo` โ how Claude reads the board and what each intent obliges. |
|
|
28
|
+
| `examples/demo/` | A minimal Vite app kept as a starting template for a consumer project. |
|
|
29
|
+
| `INSTALL.md` | Install instructions, written for Claude rather than for a human reader. |
|
|
30
|
+
| `CLAUDE_MD_SNIPPET.md` | The block to paste into each consumer project's `CLAUDE.md`. |
|
|
31
|
+
| `fingerprint.json` | SHA-256 of the whole repository and of every file (see below). |
|
|
32
|
+
| `renovate.json.example` | Renovate config so consumers get a pull request on every new tag. |
|
|
33
|
+
|
|
34
|
+
## Components
|
|
35
|
+
|
|
36
|
+
| Export | Purpose |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `DashboardThemeProvider` / `useDashboardTheme` | Turns the project palette into `--pt-color-*` variables. |
|
|
39
|
+
| `DashboardHeader` | Header with wordmark, status line and an action link. |
|
|
40
|
+
| `AlertBanner` | Full-width alert with an optional retry action. |
|
|
41
|
+
| `StatTile` / `StatTileGrid` | Large metrics with a semantic tone and a calm state for zero. |
|
|
42
|
+
| `HealthPill` / `HealthPillRow` | System facts: ok, warning, bad, unknown. |
|
|
43
|
+
| `FilterChip` / `FilterChipRow` | Filters with counters. |
|
|
44
|
+
| `Board` / `Card` / `moveCardInColumns` | Kanban board with pointer-event drag and drop and edge autoscroll. |
|
|
45
|
+
| `DetailSheet` / `DetailSection` | Bottom sheet under 760px, centred dialog above it, from one markup. |
|
|
46
|
+
| `useDelegatedClick` / `useMediaQuery` | Supporting hooks. |
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
The kit ships as a Git dependency โ no npm registry involved. Always pin a
|
|
51
|
+
tag rather than a branch, so an unfinished push cannot reach a project:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm install paneltir
|
|
55
|
+
|
|
56
|
+
# or, without npm access, straight from the repository:
|
|
57
|
+
# npm install github:daifukus/paneltir#v0.6.0
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Installing from Git runs the `prepare` script, which builds `dist/`, so the
|
|
61
|
+
repository does not need to carry build output.
|
|
62
|
+
|
|
63
|
+
### Private repository
|
|
64
|
+
|
|
65
|
+
Whoever runs `npm install` needs Git read access to this repository:
|
|
66
|
+
|
|
67
|
+
- **Locally**, an existing SSH key for GitHub is enough โ `npm install
|
|
68
|
+
github:...` reuses it.
|
|
69
|
+
- **In CI or on a deploy platform**, that machine needs its own access: a
|
|
70
|
+
read-only deploy key, or a fine-grained GitHub token scoped to this
|
|
71
|
+
repository and stored as a pipeline secret. **Never put a token in the
|
|
72
|
+
dependency URL** โ it would be committed in plain text in every consumer
|
|
73
|
+
repository. Configure it in the machine's `.npmrc` or credential helper
|
|
74
|
+
instead.
|
|
75
|
+
|
|
76
|
+
### Installing it with Claude
|
|
77
|
+
|
|
78
|
+
Paste this into the project where the kit should go:
|
|
79
|
+
|
|
80
|
+
> Install `paneltir` following the instructions in
|
|
81
|
+
> `node_modules/paneltir/INSTALL.md`, pinned to the tag we agreed on.
|
|
82
|
+
|
|
83
|
+
`INSTALL.md` is written as an instruction for the agent: it looks for the
|
|
84
|
+
palette the project already has before inventing one, maps it to the token
|
|
85
|
+
roles and wires up `DashboardThemeProvider`, so the result always looks like
|
|
86
|
+
*that* project.
|
|
87
|
+
|
|
88
|
+
`INSTALL.md` is read once, at install time. To keep the "never edit
|
|
89
|
+
`node_modules/paneltir`" rule alive across day-to-day requests, also paste the
|
|
90
|
+
block from [`CLAUDE_MD_SNIPPET.md`](./CLAUDE_MD_SNIPPET.md) into the project's
|
|
91
|
+
own `CLAUDE.md`, which is reloaded every session.
|
|
92
|
+
|
|
93
|
+
## Base themes
|
|
94
|
+
|
|
95
|
+
For a project without a visual identity yet (see `INSTALL.md`, step 3), the
|
|
96
|
+
package ships three palettes: `cyberpunkTheme` (crimson surfaces and Night
|
|
97
|
+
City yellow โ the site's default), `midnightTheme` (neutral) and
|
|
98
|
+
`oldMoneyTheme` (bottle green, muted gold). All three follow the same rules โ
|
|
99
|
+
a single accent, four semantic colours kept apart from it โ and differ only in
|
|
100
|
+
the choice of colour:
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
import { DashboardThemeProvider, cyberpunkTheme } from 'paneltir'
|
|
104
|
+
|
|
105
|
+
<DashboardThemeProvider tokens={cyberpunkTheme}>...</DashboardThemeProvider>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
They are a starting point, not an identity: replace them as soon as the
|
|
109
|
+
project has its own.
|
|
110
|
+
|
|
111
|
+
## The website
|
|
112
|
+
|
|
113
|
+
`site/` is what gets published to
|
|
114
|
+
[paneltir.dfklabs.com](https://paneltir.dfklabs.com):
|
|
115
|
+
|
|
116
|
+
- **`/`** โ the landing: what the kit solves, its public surface, the base
|
|
117
|
+
themes and how it installs.
|
|
118
|
+
- **`/demo`** โ a public panel with fictional data, so the drag and drop, the
|
|
119
|
+
filters, the detail sheet and the theme switcher can be tried without an
|
|
120
|
+
account.
|
|
121
|
+
- **`/admin`** โ the project's own panel, password protected, and fully
|
|
122
|
+
editable: add a card with the column's **+**, drag it by its grip, open it to
|
|
123
|
+
set priority, risk, who it waits on, an **order for Claude** and one of nine
|
|
124
|
+
**intents**, tick checklist steps and leave notes. **Save to GitHub** commits
|
|
125
|
+
`site/src/data/panel-state.json`, so every change to the board is a diff in
|
|
126
|
+
version control. It also carries a **Functions** section describing what the
|
|
127
|
+
kit gives a project and how Claude should work with it.
|
|
128
|
+
- **`/terms`, `/license`, `/copyright`** โ the legal pages.
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
npm install # builds dist/, which the site consumes as file:..
|
|
132
|
+
npm run site:dev # landing, demo and panel on http://localhost:5173
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Panel access
|
|
136
|
+
|
|
137
|
+
Opening `/admin` without a session lands on `/login`, a sign-in screen on the
|
|
138
|
+
site rather than the browser's own credential dialog. The password is posted to
|
|
139
|
+
`api/login.ts`, which answers with a signed, HttpOnly session cookie;
|
|
140
|
+
`middleware.ts` then verifies it before serving `/admin`, the panel's own
|
|
141
|
+
JavaScript bundle or the panel API. Sessions last eight hours, and **Sign out**
|
|
142
|
+
lives in the panel's settings sheet.
|
|
143
|
+
|
|
144
|
+
Set these in the Vercel project (Settings โ Environment Variables):
|
|
145
|
+
|
|
146
|
+
| Variable | Value |
|
|
147
|
+
| --- | --- |
|
|
148
|
+
| `ADMIN_PASSWORD` | The panel password. Required โ without it `/admin` answers 503 instead of becoming public. |
|
|
149
|
+
| `SESSION_SECRET` | Optional key for signing sessions. Defaults to `ADMIN_PASSWORD`, so changing the password ends every session already issued. |
|
|
150
|
+
| `GH_TOKEN` | Fine-grained token with *contents: read and write* on this repository only. Lets the panel commit its board. |
|
|
151
|
+
| `PANEL_REPO` | Optional `owner/repo`, `daifukus/paneltir` by default. |
|
|
152
|
+
| `PANEL_BRANCH` | Optional branch, `main` by default. |
|
|
153
|
+
|
|
154
|
+
None of these live in the repository, and `GH_TOKEN` never reaches the browser:
|
|
155
|
+
it is read inside the serverless function only.
|
|
156
|
+
|
|
157
|
+
### The board is a queue
|
|
158
|
+
|
|
159
|
+
A card is not a note, it is an instruction. Beyond a title and a body it
|
|
160
|
+
carries a **column** (`ideas`, `next`, `doing`, `yours`, `done`), a
|
|
161
|
+
**priority**, a **risk** โ what it costs if it goes wrong, not how soon it is
|
|
162
|
+
wanted โ **who it waits on**, an **order** flag meaning "I am asking you to do
|
|
163
|
+
this", and an **intent**: one of nine values that says *how* to do it and beats
|
|
164
|
+
anything the agent would otherwise decide. Three of them โ `explain`, `askme`,
|
|
165
|
+
`hold` โ are refusals to act, which is the only way to say "not yet" in a way
|
|
166
|
+
that survives a session boundary.
|
|
167
|
+
|
|
168
|
+
`.claude/skills/todo/SKILL.md` is the other half: it turns those fields into a
|
|
169
|
+
work list, in order, and says what each one obliges. Run `/todo` and the panel
|
|
170
|
+
stops being a status page and becomes the interface.
|
|
171
|
+
|
|
172
|
+
### The panel writes its own state
|
|
173
|
+
|
|
174
|
+
Moving a card in `/admin` marks the board as changed and a **Save to GitHub**
|
|
175
|
+
button appears in the header. Saving POSTs the state to
|
|
176
|
+
`/admin/api/panel-state`, which is `api/panel-state.ts`: it re-checks the same
|
|
177
|
+
credentials, validates the payload, and commits
|
|
178
|
+
`site/src/data/panel-state.json` on `main` through the GitHub API using
|
|
179
|
+
`GH_TOKEN`. So an edit made from the board is an ordinary commit with a diff,
|
|
180
|
+
an author and a history โ and the deploy that follows is what makes it visible
|
|
181
|
+
on the site.
|
|
182
|
+
|
|
183
|
+
The write sends the file's current blob SHA, so if the file changed in the
|
|
184
|
+
repository meanwhile the save is rejected with a conflict instead of silently
|
|
185
|
+
overwriting that change.
|
|
186
|
+
|
|
187
|
+
## Basic usage
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
import {
|
|
191
|
+
DashboardThemeProvider,
|
|
192
|
+
DashboardHeader,
|
|
193
|
+
StatTileGrid,
|
|
194
|
+
StatTile,
|
|
195
|
+
Board,
|
|
196
|
+
Card,
|
|
197
|
+
DetailSheet,
|
|
198
|
+
DetailSection,
|
|
199
|
+
moveCardInColumns,
|
|
200
|
+
type DashboardThemeTokens,
|
|
201
|
+
type BoardColumnData,
|
|
202
|
+
} from 'paneltir'
|
|
203
|
+
import 'paneltir/style.css'
|
|
204
|
+
|
|
205
|
+
// The project palette โ the only thing that changes between projects.
|
|
206
|
+
const theme: DashboardThemeTokens = {
|
|
207
|
+
ground: '#0b0c10',
|
|
208
|
+
groundRaised: '#14161c',
|
|
209
|
+
panel: '#181a21',
|
|
210
|
+
raised: '#20232b',
|
|
211
|
+
line: 'rgba(255,255,255,0.13)',
|
|
212
|
+
lineFaint: 'rgba(255,255,255,0.07)',
|
|
213
|
+
ink: 'rgba(255,255,255,0.92)',
|
|
214
|
+
inkDim: 'rgba(255,255,255,0.62)',
|
|
215
|
+
inkFaint: 'rgba(255,255,255,0.34)',
|
|
216
|
+
accent: '#5b8cff',
|
|
217
|
+
danger: '#ff5c5c',
|
|
218
|
+
warning: '#f5b74c',
|
|
219
|
+
success: '#3ecf8e',
|
|
220
|
+
neutral: '#8a8f98',
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function AdminDashboard() {
|
|
224
|
+
const [columns, setColumns] = React.useState<BoardColumnData[]>(initialColumns)
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<DashboardThemeProvider tokens={theme}>
|
|
228
|
+
<DashboardHeader wordmark="My project" status="3 pending" linkLabel="Sign out" />
|
|
229
|
+
|
|
230
|
+
<StatTileGrid>
|
|
231
|
+
<StatTile label="Open" value={12} tone="warning" />
|
|
232
|
+
<StatTile label="Overdue" value={0} isZero />
|
|
233
|
+
</StatTileGrid>
|
|
234
|
+
|
|
235
|
+
<Board
|
|
236
|
+
columns={columns}
|
|
237
|
+
onMove={(move) => setColumns((current) => moveCardInColumns(current, move))}
|
|
238
|
+
renderCard={(card, index, columnId) => (
|
|
239
|
+
<Card
|
|
240
|
+
cardId={card.id}
|
|
241
|
+
columnId={columnId}
|
|
242
|
+
index={index}
|
|
243
|
+
title={card.title as string}
|
|
244
|
+
priorityColor={theme.danger}
|
|
245
|
+
tags={[{ label: 'blocked', tone: 'danger', emphasize: true }]}
|
|
246
|
+
progress={{ done: 2, total: 5 }}
|
|
247
|
+
onClick={() => openDetail(card.id)}
|
|
248
|
+
/>
|
|
249
|
+
)}
|
|
250
|
+
/>
|
|
251
|
+
|
|
252
|
+
<DetailSheet open={detailOpen} onClose={closeDetail} ariaLabel="Card detail">
|
|
253
|
+
<DetailSection title="Assigned to" subtitle="who is working on it">
|
|
254
|
+
...
|
|
255
|
+
</DetailSection>
|
|
256
|
+
</DetailSheet>
|
|
257
|
+
</DashboardThemeProvider>
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## What the library actually solves
|
|
263
|
+
|
|
264
|
+
- **Pointer-event drag and drop**, not the HTML5 drag API, which never fires
|
|
265
|
+
on iOS touch.
|
|
266
|
+
- **Edge autoscroll on requestAnimationFrame**, reading the pointer from a
|
|
267
|
+
ref rather than from `pointermove`, which stops firing while a finger is
|
|
268
|
+
held still against the edge.
|
|
269
|
+
- **Scroll-snap disabled during a drag**, so the autoscroll does not fight the
|
|
270
|
+
board's snap.
|
|
271
|
+
- **A real floating clone** (a DOM clone, not a redrawn approximation) that
|
|
272
|
+
follows the pointer.
|
|
273
|
+
- **A single `moveCardInColumns`** โ moving an item between arrays by index is
|
|
274
|
+
exactly where an off-by-one hides when four projects each rewrite it.
|
|
275
|
+
|
|
276
|
+
## Theming
|
|
277
|
+
|
|
278
|
+
All colour enters through `DashboardThemeTokens` and leaves as `--pt-color-*`
|
|
279
|
+
variables inside `.pt-theme-root`. The library ships no defaults on purpose:
|
|
280
|
+
a forgotten token renders without colour, which is noticed immediately,
|
|
281
|
+
instead of silently inheriting something generic. Every role is documented in
|
|
282
|
+
`src/theme/tokens.ts` and in the published `.d.ts`.
|
|
283
|
+
|
|
284
|
+
## Change fingerprint (`fingerprint.json`)
|
|
285
|
+
|
|
286
|
+
`fingerprint.json` holds a SHA-256 computed over every tracked file, plus a
|
|
287
|
+
hash per file. It ships with the package, so in a consumer project it sits at
|
|
288
|
+
`node_modules/paneltir/fingerprint.json`.
|
|
289
|
+
|
|
290
|
+
It exists so Claude โ or any script โ can tell in a single read whether the
|
|
291
|
+
kit changed since it was last reviewed, instead of spending tokens re-reading
|
|
292
|
+
code and documentation that did not move. The block in `CLAUDE_MD_SNIPPET.md`
|
|
293
|
+
records the last integrated hash in each project; when it matches, there is
|
|
294
|
+
nothing to read. When it differs, the `files` map names exactly which files
|
|
295
|
+
moved.
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
npm run fingerprint # regenerate it (before every commit)
|
|
299
|
+
npm run fingerprint:check # fail if stale (this is what CI runs)
|
|
300
|
+
node scripts/fingerprint.mjs --print # print just the hash
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
It is only rewritten when the global hash changes, so it never produces empty
|
|
304
|
+
diffs.
|
|
305
|
+
|
|
306
|
+
## Deployment (Vercel)
|
|
307
|
+
|
|
308
|
+
The site is served from Vercel. The configuration lives in `vercel.json`; in
|
|
309
|
+
the Vercel project leave **Root Directory empty** (the repository root) and
|
|
310
|
+
Framework Preset as "Other": the install builds the kit (`dist/`) first and
|
|
311
|
+
the site second, and the output is `site/dist`. Every merge to `main` deploys.
|
|
312
|
+
Remember to set `ADMIN_PASSWORD` before the first deploy.
|
|
313
|
+
|
|
314
|
+
Changing a Vercel setting only affects new deployments: redeploy, or merge a
|
|
315
|
+
pull request, for it to take effect. Keep the repository connected to a single
|
|
316
|
+
Vercel project โ two projects on the same repository build twice and only one
|
|
317
|
+
can hold the domain.
|
|
318
|
+
|
|
319
|
+
### Only `main` deploys
|
|
320
|
+
|
|
321
|
+
`vercel.json` sets `git.deploymentEnabled` so Vercel builds `main` and nothing
|
|
322
|
+
else. Branch previews were spending the free plan's allowance of 100
|
|
323
|
+
deployments a day โ once it runs out, *nothing* deploys for 24 hours,
|
|
324
|
+
production included. Pull requests lose their preview URL, which costs little:
|
|
325
|
+
CI already builds the kit, the site and the starter template on every pull
|
|
326
|
+
request.
|
|
327
|
+
|
|
328
|
+
If previews are wanted back, delete that `git` block, and keep an eye on the
|
|
329
|
+
daily count. The same switch exists in the dashboard under Settings โ Git,
|
|
330
|
+
and the dashboard is authoritative if the two ever disagree.
|
|
331
|
+
|
|
332
|
+
### If production never deploys
|
|
333
|
+
|
|
334
|
+
Vercel refuses to build a commit whose author GitHub cannot verify, and it
|
|
335
|
+
says so on the pull request ("GitHub couldn't verify an account for commit").
|
|
336
|
+
Merge commits created from the GitHub UI or API carry the account's primary
|
|
337
|
+
email, so if that address is a private relay the production build is blocked
|
|
338
|
+
while branch previews still work.
|
|
339
|
+
|
|
340
|
+
Two fixes, and doing both is fine:
|
|
341
|
+
|
|
342
|
+
1. **At the source.** GitHub โ Settings โ Emails โ enable *Keep my email
|
|
343
|
+
address private*. From then on web and API commits are authored as
|
|
344
|
+
`<id>+<user>@users.noreply.github.com`, which is always verifiable.
|
|
345
|
+
2. **Independently of Git.** `.github/workflows/deploy.yml` pings a Vercel
|
|
346
|
+
deploy hook on every push to `main`. Create the hook in Vercel โ Settings โ
|
|
347
|
+
Git โ Deploy Hooks (branch `main`) and store its URL as the
|
|
348
|
+
`VERCEL_DEPLOY_HOOK_URL` secret in this repository. Without the secret the
|
|
349
|
+
job is a no-op, so nothing breaks if it is not configured.
|
|
350
|
+
|
|
351
|
+
### Does the build need a GitHub token?
|
|
352
|
+
|
|
353
|
+
The **build** does not: the site consumes the kit as `file:..`, so Vercel never
|
|
354
|
+
fetches this repository over the network, and its GitHub integration already
|
|
355
|
+
has read access to the source.
|
|
356
|
+
|
|
357
|
+
The **panel** does, to write: `GH_TOKEN` is what lets `api/panel-state.ts`
|
|
358
|
+
commit the board. Keep it fine-grained, scoped to this repository, with
|
|
359
|
+
*contents: read and write* and nothing else. It is only ever read server-side.
|
|
360
|
+
|
|
361
|
+
## Updating consumer projects
|
|
362
|
+
|
|
363
|
+
### By hand
|
|
364
|
+
|
|
365
|
+
1. Make the change here with the version bump in `package.json`, merge it to
|
|
366
|
+
`main`, and `tag-release.yml` creates the `vX.Y.Z` tag automatically.
|
|
367
|
+
2. In each consumer project: `npm install paneltir@X.Y.Z`, or
|
|
368
|
+
`npm install github:daifukus/paneltir#vX.Y.Z` without npm access.
|
|
369
|
+
3. Claude can do that bump and run the dashboard smoke test in each
|
|
370
|
+
repository โ it is mechanical, not a reinterpretation.
|
|
371
|
+
|
|
372
|
+
### With automatic pull requests
|
|
373
|
+
|
|
374
|
+
Install the [Renovate app](https://github.com/apps/renovate) on each consumer
|
|
375
|
+
project and copy [`renovate.json.example`](./renovate.json.example) as
|
|
376
|
+
`renovate.json` at its root. From then on, every new tag opens a pull request
|
|
377
|
+
with the bump already applied.
|
|
378
|
+
|
|
379
|
+
## Local development
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
npm install
|
|
383
|
+
npm run dev # tsup --watch
|
|
384
|
+
npm run typecheck # tsc --noEmit
|
|
385
|
+
npm run build # produces dist/
|
|
386
|
+
npm run verify # typecheck + build + fingerprint:check (what CI runs)
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
To try it against a project before tagging, `npm link` (or a temporary
|
|
390
|
+
`file:../paneltir` dependency) avoids tagging on every iteration.
|
|
391
|
+
|
|
392
|
+
## Roadmap
|
|
393
|
+
|
|
394
|
+
- Type sizes as `--pt-font-size-*` tokens instead of per-component values.
|
|
395
|
+
- Drop placeholder matching the dragged card's real height.
|
|
396
|
+
- Tests for `moveCardInColumns` (same index, end of column, empty columns).
|
|
397
|
+
- Validate the token system against light surfaces.
|
|
398
|
+
|
|
399
|
+
## License
|
|
400
|
+
|
|
401
|
+
Copyright ยฉ 2026 DFKlabs. All rights reserved. Use is restricted to the
|
|
402
|
+
projects the holder authorises โ see [`LICENSE`](./LICENSE) for the full
|
|
403
|
+
terms, and the [License page](https://paneltir.dfklabs.com/license) for a
|
|
404
|
+
plain-English summary.
|