paneltir 0.12.0 → 0.13.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 +16 -0
- package/INSTALL.md +2 -2
- package/README.md +1 -1
- package/bin/paneltir.mjs +18 -1
- package/dist/index.d.ts +632 -535
- package/dist/index.js +217 -41
- package/fingerprint.json +5 -5
- package/package.json +11 -25
- package/template/.claude/skills/panel/SKILL.md +50 -0
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// src/version.ts
|
|
2
|
-
var PANELTIR_VERSION = "0.
|
|
3
|
-
var PANELTIR_FINGERPRINT = "sha256:
|
|
4
|
-
var PANELTIR_FILE_COUNT =
|
|
5
|
-
var PANELTIR_TEMPLATE_HASH = "sha256:
|
|
2
|
+
var PANELTIR_VERSION = "0.13.0";
|
|
3
|
+
var PANELTIR_FINGERPRINT = "sha256:f3b5664bddf68f9560bd368b01184f97c733b9a5a3706507ae2219c6a41d9a1c";
|
|
4
|
+
var PANELTIR_FILE_COUNT = 167;
|
|
5
|
+
var PANELTIR_TEMPLATE_HASH = "sha256:7c204bad5c7a8b329ad53ac51b5856438fe84735cea47543eb36b976edbb27da";
|
|
6
6
|
var paneltirBuild = {
|
|
7
7
|
version: PANELTIR_VERSION,
|
|
8
8
|
fingerprint: PANELTIR_FINGERPRINT,
|
|
@@ -348,11 +348,11 @@ function Column({ column, renderCard, onAddCard }) {
|
|
|
348
348
|
column.cards.map((card, index) => {
|
|
349
349
|
const showBefore = dnd.isDragging && dnd.dropTarget?.columnId === column.id && dnd.dropTarget.index === index;
|
|
350
350
|
return /* @__PURE__ */ jsxs7(Fragment3, { children: [
|
|
351
|
-
showBefore && /* @__PURE__ */ jsx9("div", { className: "pt-card-placeholder" }),
|
|
351
|
+
showBefore && /* @__PURE__ */ jsx9("div", { className: "pt-card-placeholder", "data-pt-placeholder": true }),
|
|
352
352
|
renderCard(card, index, column.id)
|
|
353
353
|
] }, card.id);
|
|
354
354
|
}),
|
|
355
|
-
dnd.isDragging && dnd.dropTarget?.columnId === column.id && dnd.dropTarget.index === column.cards.length && /* @__PURE__ */ jsx9("div", { className: "pt-card-placeholder" }),
|
|
355
|
+
dnd.isDragging && dnd.dropTarget?.columnId === column.id && dnd.dropTarget.index === column.cards.length && /* @__PURE__ */ jsx9("div", { className: "pt-card-placeholder", "data-pt-placeholder": true }),
|
|
356
356
|
column.footer ? /* @__PURE__ */ jsx9("div", { className: "pt-column__footer", children: column.footer }) : null
|
|
357
357
|
] })
|
|
358
358
|
] });
|
|
@@ -406,6 +406,11 @@ function useAutoscroll({ active, pointerRef, scrollContainerRef, edgeSize = 56,
|
|
|
406
406
|
}
|
|
407
407
|
|
|
408
408
|
// src/components/Board/useBoardDnD.ts
|
|
409
|
+
function dropIndex(source, target) {
|
|
410
|
+
if (source.columnId !== target.columnId) return target.index;
|
|
411
|
+
if (target.index === source.index || target.index === source.index + 1) return null;
|
|
412
|
+
return target.index > source.index ? target.index - 1 : target.index;
|
|
413
|
+
}
|
|
409
414
|
function useBoardDnD({ columns, onMove, scrollContainerRef }) {
|
|
410
415
|
const [drag, setDrag] = useState(null);
|
|
411
416
|
const [pointer, setPointer] = useState(null);
|
|
@@ -466,6 +471,7 @@ function useBoardDnD({ columns, onMove, scrollContainerRef }) {
|
|
|
466
471
|
if (!listEl) return;
|
|
467
472
|
const columnId = listEl.getAttribute("data-pt-column-list");
|
|
468
473
|
const cardEl = el?.closest("[data-pt-card]");
|
|
474
|
+
if (el?.closest("[data-pt-placeholder]")) return;
|
|
469
475
|
let index;
|
|
470
476
|
if (cardEl && listEl.contains(cardEl)) {
|
|
471
477
|
const cardRect = cardEl.getBoundingClientRect();
|
|
@@ -492,13 +498,13 @@ function useBoardDnD({ columns, onMove, scrollContainerRef }) {
|
|
|
492
498
|
const finalTarget = dropTargetRef.current;
|
|
493
499
|
const source = dragRef.current;
|
|
494
500
|
if (finalTarget && source) {
|
|
495
|
-
const
|
|
496
|
-
if (
|
|
501
|
+
const toIndex = dropIndex(source, finalTarget);
|
|
502
|
+
if (toIndex !== null) {
|
|
497
503
|
onMove({
|
|
498
504
|
cardId: source.cardId,
|
|
499
505
|
fromColumnId: source.columnId,
|
|
500
506
|
toColumnId: finalTarget.columnId,
|
|
501
|
-
toIndex
|
|
507
|
+
toIndex
|
|
502
508
|
});
|
|
503
509
|
}
|
|
504
510
|
}
|
|
@@ -607,10 +613,27 @@ function moveCardInColumns(columns, move) {
|
|
|
607
613
|
});
|
|
608
614
|
}
|
|
609
615
|
|
|
616
|
+
// src/panel/requests.ts
|
|
617
|
+
function slug(subject) {
|
|
618
|
+
return subject.replace(/[^A-Za-z0-9._]/g, "-");
|
|
619
|
+
}
|
|
620
|
+
function requestId(kind, day, subject) {
|
|
621
|
+
return subject === void 0 ? `${kind}-${day}` : `${kind}-${slug(subject)}-${day}`;
|
|
622
|
+
}
|
|
623
|
+
function requestKey(id) {
|
|
624
|
+
const match = /^(.+)-\d{4}-\d{2}-\d{2}$/.exec(id);
|
|
625
|
+
return match ? match[1] : null;
|
|
626
|
+
}
|
|
627
|
+
function pendingRequest(cards, kind, doneColumns, subject) {
|
|
628
|
+
const wanted = subject === void 0 ? kind : `${kind}-${slug(subject)}`;
|
|
629
|
+
return cards.find((card) => !doneColumns.has(card.column) && requestKey(card.id) === wanted);
|
|
630
|
+
}
|
|
631
|
+
|
|
610
632
|
// src/components/DetailSheet/DetailSheet.tsx
|
|
611
|
-
import { useEffect as useEffect3 } from "react";
|
|
633
|
+
import { useEffect as useEffect3, useRef as useRef4 } from "react";
|
|
612
634
|
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
613
635
|
function DetailSheet({ open, onClose, children, ariaLabel }) {
|
|
636
|
+
const pressedBackdrop = useRef4(false);
|
|
614
637
|
useEffect3(() => {
|
|
615
638
|
if (!open) return;
|
|
616
639
|
function handleKey(e) {
|
|
@@ -620,10 +643,24 @@ function DetailSheet({ open, onClose, children, ariaLabel }) {
|
|
|
620
643
|
return () => window.removeEventListener("keydown", handleKey);
|
|
621
644
|
}, [open, onClose]);
|
|
622
645
|
if (!open) return null;
|
|
623
|
-
return /* @__PURE__ */ jsx12(
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
646
|
+
return /* @__PURE__ */ jsx12(
|
|
647
|
+
"div",
|
|
648
|
+
{
|
|
649
|
+
className: "pt-sheet-backdrop",
|
|
650
|
+
onPointerDown: (e) => {
|
|
651
|
+
pressedBackdrop.current = e.target === e.currentTarget;
|
|
652
|
+
},
|
|
653
|
+
onClick: (e) => {
|
|
654
|
+
const began = pressedBackdrop.current;
|
|
655
|
+
pressedBackdrop.current = false;
|
|
656
|
+
if (began && e.target === e.currentTarget) onClose();
|
|
657
|
+
},
|
|
658
|
+
children: /* @__PURE__ */ jsxs10("div", { className: "pt-sheet", role: "dialog", "aria-modal": "true", "aria-label": ariaLabel, onClick: (e) => e.stopPropagation(), children: [
|
|
659
|
+
/* @__PURE__ */ jsx12("div", { className: "pt-sheet__handle" }),
|
|
660
|
+
children
|
|
661
|
+
] })
|
|
662
|
+
}
|
|
663
|
+
);
|
|
627
664
|
}
|
|
628
665
|
function DetailSection({ title, subtitle, children }) {
|
|
629
666
|
return /* @__PURE__ */ jsxs10("section", { className: "pt-detail-section", children: [
|
|
@@ -739,7 +776,7 @@ function GuideTour({ notes, dismissed, onDismiss }) {
|
|
|
739
776
|
}
|
|
740
777
|
|
|
741
778
|
// src/components/Notifications/Notifications.tsx
|
|
742
|
-
import { useCallback as useCallback3, useEffect as useEffect5, useMemo as useMemo2, useRef as
|
|
779
|
+
import { useCallback as useCallback3, useEffect as useEffect5, useMemo as useMemo2, useRef as useRef5, useState as useState3 } from "react";
|
|
743
780
|
import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
744
781
|
var STORE_KEY = "pt-seen";
|
|
745
782
|
function readSeen() {
|
|
@@ -768,7 +805,7 @@ function NotificationBell({ notifications, labels, seen: given, onSeen }) {
|
|
|
768
805
|
const controlled = given !== void 0;
|
|
769
806
|
const [stored, setStored] = useState3(null);
|
|
770
807
|
const [open, setOpen] = useState3(false);
|
|
771
|
-
const root =
|
|
808
|
+
const root = useRef5(null);
|
|
772
809
|
useEffect5(() => {
|
|
773
810
|
setStored(readSeen());
|
|
774
811
|
}, []);
|
|
@@ -873,7 +910,7 @@ function NotificationBell({ notifications, labels, seen: given, onSeen }) {
|
|
|
873
910
|
}
|
|
874
911
|
|
|
875
912
|
// src/components/CopyBlock/CopyBlock.tsx
|
|
876
|
-
import { useCallback as useCallback4, useEffect as useEffect6, useRef as
|
|
913
|
+
import { useCallback as useCallback4, useEffect as useEffect6, useRef as useRef6, useState as useState4 } from "react";
|
|
877
914
|
import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
878
915
|
var DEFAULT_LABELS2 = {
|
|
879
916
|
copy: "Copy",
|
|
@@ -883,8 +920,8 @@ var DEFAULT_LABELS2 = {
|
|
|
883
920
|
function CopyBlock({ text: text2, label, note, labels }) {
|
|
884
921
|
const words = { ...DEFAULT_LABELS2, ...labels };
|
|
885
922
|
const [result, setResult] = useState4("idle");
|
|
886
|
-
const pre =
|
|
887
|
-
const timer =
|
|
923
|
+
const pre = useRef6(null);
|
|
924
|
+
const timer = useRef6(void 0);
|
|
888
925
|
useEffect6(() => () => window.clearTimeout(timer.current), []);
|
|
889
926
|
const announce = useCallback4((next) => {
|
|
890
927
|
setResult(next);
|
|
@@ -1217,6 +1254,12 @@ function stampForColumn(card, columns) {
|
|
|
1217
1254
|
if (column.active && !next.startedAt) next.startedAt = today();
|
|
1218
1255
|
return next;
|
|
1219
1256
|
}
|
|
1257
|
+
function queueColumn(columns) {
|
|
1258
|
+
const next = columns.find((column) => column.id === "next");
|
|
1259
|
+
if (next) return next.id;
|
|
1260
|
+
const queued = columns.find((column) => !column.active && !column.done && column.id !== "yours");
|
|
1261
|
+
return (queued ?? columns[0])?.id ?? "next";
|
|
1262
|
+
}
|
|
1220
1263
|
function claimedWithoutStarting(card) {
|
|
1221
1264
|
return Boolean(card.completedAt) && !card.startedAt;
|
|
1222
1265
|
}
|
|
@@ -1599,6 +1642,28 @@ var UI = {
|
|
|
1599
1642
|
releaseNone: "This version was published without notes.",
|
|
1600
1643
|
updateCardTitle: (version) => `Update Paneltir to v${version}`,
|
|
1601
1644
|
updateCardBody: (version) => `Run \`npm install paneltir@${version}\` in this project, then check the panel still builds and renders. The version in use is reported by \`paneltirBuild\`, so it can be confirmed rather than assumed.`,
|
|
1645
|
+
versionBumpAsk: "Ask for a version bump",
|
|
1646
|
+
versionBumpOpen: "A version bump is already asked for \u2014 open it",
|
|
1647
|
+
versionBumpHow: "Writes an order card: every target up one version and one build, kept in step. Nothing changes until a session does it, and the card is dirty until Save like any edit.",
|
|
1648
|
+
versionBumpTitle: "Bump the version by one, on every target",
|
|
1649
|
+
versionBumpBody: "Asked from the panel. Move the version up one step and the build number up one on every target this project ships \u2014 a web app\u2019s package.json, an iOS app\u2019s MARKETING_VERSION and CURRENT_PROJECT_VERSION, an Android app\u2019s versionName and versionCode, and anything else that carries one \u2014 so every target ends on the same version and the same build. For example: build 18, version 1.0.18 becomes build 19, version 1.0.19. A target with no build number moves its version only. If the targets already disagree, bring them all to the highest plus one and say which were behind. Follow this project\u2019s own release rules \u2014 changelog, tag, notes \u2014 if it has any.",
|
|
1650
|
+
versionBumpChecks: [
|
|
1651
|
+
"Found every target that carries a version or a build number",
|
|
1652
|
+
"Moved each one: version up one step, build up one",
|
|
1653
|
+
"Every target ends on the same version and the same build"
|
|
1654
|
+
],
|
|
1655
|
+
installAsk: "Add a card asking Claude to install it",
|
|
1656
|
+
installAskOpen: "Already asked for \u2014 open the card",
|
|
1657
|
+
installAskHow: "The card carries the repository snippet above, so the session that picks it up installs exactly this. It is dirty until Save, like any edit.",
|
|
1658
|
+
installCardTitle: (tool) => `Install ${tool} in this repository`,
|
|
1659
|
+
installCardBody: (tool, file, snippet) => `Asked from the Tools screen. Merge these two keys into ${file} \u2014 merge, never replace: the file holds other settings. Then commit it, so ${tool} installs for anyone who starts a session in this repository.
|
|
1660
|
+
|
|
1661
|
+
${snippet}`,
|
|
1662
|
+
installCardChecks: [
|
|
1663
|
+
"Merged into the settings file without replacing anything already there",
|
|
1664
|
+
"Committed to the repository",
|
|
1665
|
+
"The Tools screen shows it as Active"
|
|
1666
|
+
],
|
|
1602
1667
|
cardDetail: "Card",
|
|
1603
1668
|
panelSettings: "Panel settings",
|
|
1604
1669
|
title: "Title",
|
|
@@ -1934,6 +1999,28 @@ var UI = {
|
|
|
1934
1999
|
releaseNone: "Esta versi\xF3n se public\xF3 sin notas.",
|
|
1935
2000
|
updateCardTitle: (version) => `Actualizar Paneltir a la v${version}`,
|
|
1936
2001
|
updateCardBody: (version) => `Ejecuta \`npm install paneltir@${version}\` en este proyecto y comprueba que el panel sigue construy\xE9ndose y dibuj\xE1ndose. La versi\xF3n en uso la reporta \`paneltirBuild\`, as\xED que se puede confirmar en vez de darla por hecha.`,
|
|
2002
|
+
versionBumpAsk: "Pedir subir versi\xF3n",
|
|
2003
|
+
versionBumpOpen: "Ya hay una subida de versi\xF3n pedida \u2014 abrirla",
|
|
2004
|
+
versionBumpHow: "Escribe una tarjeta de orden: todos los targets suben una versi\xF3n y un build, a la par. No cambia nada hasta que una sesi\xF3n lo haga, y la tarjeta queda sin guardar hasta Guardar, como cualquier edici\xF3n.",
|
|
2005
|
+
versionBumpTitle: "Subir la versi\xF3n en uno, en todos los targets",
|
|
2006
|
+
versionBumpBody: "Pedido desde el panel. Sube la versi\xF3n un paso y el n\xFAmero de build en uno en todos los targets que publica este proyecto \u2014 el package.json de una web, MARKETING_VERSION y CURRENT_PROJECT_VERSION de una app de iOS, versionName y versionCode de una de Android, y cualquier otro que lleve uno \u2014 para que todos acaben en la misma versi\xF3n y el mismo build. Por ejemplo: build 18, versi\xF3n 1.0.18 pasa a build 19, versi\xF3n 1.0.19. Un target sin n\xFAmero de build mueve solo la versi\xF3n. Si los targets ya no coinciden, ll\xE9valos todos al m\xE1s alto m\xE1s uno y di cu\xE1les iban por detr\xE1s. Sigue las reglas de release del propio proyecto \u2014 changelog, tag, notas \u2014 si las tiene.",
|
|
2007
|
+
versionBumpChecks: [
|
|
2008
|
+
"Encontrados todos los targets que llevan versi\xF3n o n\xFAmero de build",
|
|
2009
|
+
"Movido cada uno: versi\xF3n un paso arriba, build uno arriba",
|
|
2010
|
+
"Todos los targets acaban en la misma versi\xF3n y el mismo build"
|
|
2011
|
+
],
|
|
2012
|
+
installAsk: "A\xF1adir una tarjeta para que Claude lo instale",
|
|
2013
|
+
installAskOpen: "Ya pedido \u2014 abrir la tarjeta",
|
|
2014
|
+
installAskHow: "La tarjeta lleva el fragmento del repositorio de arriba, as\xED que la sesi\xF3n que la recoja instala exactamente esto. Queda sin guardar hasta Guardar, como cualquier edici\xF3n.",
|
|
2015
|
+
installCardTitle: (tool) => `Instalar ${tool} en este repositorio`,
|
|
2016
|
+
installCardBody: (tool, file, snippet) => `Pedido desde la pantalla de Herramientas. Fusiona estas dos claves en ${file} \u2014 fusiona, nunca reemplaces: el archivo lleva otros ajustes. Despu\xE9s haz commit, para que ${tool} se instale para cualquiera que abra una sesi\xF3n en este repositorio.
|
|
2017
|
+
|
|
2018
|
+
${snippet}`,
|
|
2019
|
+
installCardChecks: [
|
|
2020
|
+
"Fusionado en el archivo de ajustes sin reemplazar nada de lo que ya hab\xEDa",
|
|
2021
|
+
"Commiteado en el repositorio",
|
|
2022
|
+
"La pantalla de Herramientas lo muestra como Activo"
|
|
2023
|
+
],
|
|
1937
2024
|
cardDetail: "Tarjeta",
|
|
1938
2025
|
panelSettings: "Ajustes del panel",
|
|
1939
2026
|
title: "T\xEDtulo",
|
|
@@ -2676,7 +2763,16 @@ function ToolRow({
|
|
|
2676
2763
|
tool.state === "misconfigured" && /* @__PURE__ */ jsx19("p", { className: "pt-warn", children: ui.stateMisconfiguredWhy })
|
|
2677
2764
|
] });
|
|
2678
2765
|
}
|
|
2679
|
-
function MarketplaceView({
|
|
2766
|
+
function MarketplaceView({
|
|
2767
|
+
report,
|
|
2768
|
+
loading,
|
|
2769
|
+
failure,
|
|
2770
|
+
lang,
|
|
2771
|
+
ui,
|
|
2772
|
+
onRefresh,
|
|
2773
|
+
onRequestInstall,
|
|
2774
|
+
installRequested
|
|
2775
|
+
}) {
|
|
2680
2776
|
const [filter, setFilter] = useState8("all");
|
|
2681
2777
|
const [open, setOpen] = useState8(null);
|
|
2682
2778
|
const tools = report?.tools ?? [];
|
|
@@ -2766,7 +2862,12 @@ function MarketplaceView({ report, loading, failure, lang, ui, onRefresh }) {
|
|
|
2766
2862
|
tool: open,
|
|
2767
2863
|
ui,
|
|
2768
2864
|
marketplace: report?.marketplace ?? { name: MARKETPLACE_NAME, url: MARKETPLACE_URL },
|
|
2769
|
-
registered: report?.project.marketplaceRegistered ?? false
|
|
2865
|
+
registered: report?.project.marketplaceRegistered ?? false,
|
|
2866
|
+
requested: installRequested?.(open.id) ?? false,
|
|
2867
|
+
onRequest: onRequestInstall ? (marketplace) => {
|
|
2868
|
+
onRequestInstall(open, marketplace);
|
|
2869
|
+
setOpen(null);
|
|
2870
|
+
} : void 0
|
|
2770
2871
|
}
|
|
2771
2872
|
),
|
|
2772
2873
|
/* @__PURE__ */ jsx19("button", { type: "button", className: "pt-btn pt-btn--sm", onClick: () => setOpen(null), children: ui.close })
|
|
@@ -2777,20 +2878,28 @@ function InstallSheet({
|
|
|
2777
2878
|
tool,
|
|
2778
2879
|
ui,
|
|
2779
2880
|
marketplace,
|
|
2780
|
-
registered
|
|
2881
|
+
registered,
|
|
2882
|
+
requested,
|
|
2883
|
+
onRequest
|
|
2781
2884
|
}) {
|
|
2782
2885
|
const copy = { copy: ui.copy, copied: ui.copied, selected: ui.copySelected };
|
|
2783
2886
|
return /* @__PURE__ */ jsxs17(Fragment5, { children: [
|
|
2784
2887
|
tool.claudePlugin ? /* @__PURE__ */ jsxs17(Fragment5, { children: [
|
|
2785
|
-
/* @__PURE__ */
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2888
|
+
/* @__PURE__ */ jsxs17(DetailSection, { title: ui.installRepoTitle, subtitle: ui.installRepoHint, children: [
|
|
2889
|
+
/* @__PURE__ */ jsx19(
|
|
2890
|
+
CopyBlock,
|
|
2891
|
+
{
|
|
2892
|
+
text: repositorySnippet(tool.id, marketplace),
|
|
2893
|
+
label: SETTINGS_PATH,
|
|
2894
|
+
note: registered ? ui.installRepoMergeRegistered : ui.installRepoMerge,
|
|
2895
|
+
labels: copy
|
|
2896
|
+
}
|
|
2897
|
+
),
|
|
2898
|
+
onRequest && tool.state !== "active" && /* @__PURE__ */ jsxs17("div", { className: "pt-panel-row", style: { marginTop: 10 }, children: [
|
|
2899
|
+
/* @__PURE__ */ jsx19("button", { type: "button", className: "pt-btn pt-btn--sm pt-btn--primary", onClick: () => onRequest(marketplace), children: requested ? ui.installAskOpen : ui.installAsk }),
|
|
2900
|
+
/* @__PURE__ */ jsx19("p", { className: "pt-marketplace__detail", children: ui.installAskHow })
|
|
2901
|
+
] })
|
|
2902
|
+
] }),
|
|
2794
2903
|
/* @__PURE__ */ jsx19(DetailSection, { title: ui.installCliTitle, subtitle: ui.installCliHint, children: /* @__PURE__ */ jsx19(CopyBlock, { text: commandSnippet(tool.id, marketplace), labels: copy }) })
|
|
2795
2904
|
] }) : /* @__PURE__ */ jsx19("p", { className: "pt-panel-text", children: ui.installNoPlugin }),
|
|
2796
2905
|
tool.standaloneCommand ? /* @__PURE__ */ jsx19(DetailSection, { title: ui.installStandaloneTitle, subtitle: ui.installStandaloneHint, children: /* @__PURE__ */ jsx19(CopyBlock, { text: tool.standaloneCommand, labels: copy }) }) : /* @__PURE__ */ jsx19(DetailSection, { title: ui.installStandaloneTitle, children: /* @__PURE__ */ jsx19("p", { className: "pt-panel-text", children: ui.installNoStandalone }) }),
|
|
@@ -3350,18 +3459,18 @@ function PanelApp({
|
|
|
3350
3459
|
}, []);
|
|
3351
3460
|
const addCard = useCallback5(
|
|
3352
3461
|
(columnId) => {
|
|
3353
|
-
const card = emptyCard(columnId, state.areas[0]?.id ?? "general");
|
|
3462
|
+
const card = stampForColumn(emptyCard(columnId, state.areas[0]?.id ?? "general"), state.columns);
|
|
3354
3463
|
setState((current) => ({ ...current, updatedAt: today(), cards: [...current.cards, card] }));
|
|
3355
3464
|
setOpenCardId(card.id);
|
|
3356
3465
|
},
|
|
3357
|
-
[state.areas]
|
|
3466
|
+
[state.areas, state.columns]
|
|
3358
3467
|
);
|
|
3359
3468
|
const addUpdateCard = useCallback5(() => {
|
|
3360
3469
|
if (!update?.behind) return;
|
|
3361
3470
|
const id = updateCardId(update.latest);
|
|
3362
3471
|
if (state.cards.some((card2) => card2.id === id)) return;
|
|
3363
3472
|
const card = {
|
|
3364
|
-
...emptyCard(
|
|
3473
|
+
...emptyCard(queueColumn(state.columns), state.areas[0]?.id ?? "general"),
|
|
3365
3474
|
id,
|
|
3366
3475
|
title: { en: UI.en.updateCardTitle(update.latest), es: UI.es.updateCardTitle(update.latest) },
|
|
3367
3476
|
body: { en: UI.en.updateCardBody(update.latest), es: UI.es.updateCardBody(update.latest) },
|
|
@@ -3372,7 +3481,7 @@ function PanelApp({
|
|
|
3372
3481
|
};
|
|
3373
3482
|
setState((current) => ({ ...current, updatedAt: today(), cards: [...current.cards, card] }));
|
|
3374
3483
|
setOpenCardId(id);
|
|
3375
|
-
}, [update, state.cards, state.areas, state.preferences?.updateAsOrder]);
|
|
3484
|
+
}, [update, state.cards, state.areas, state.columns, state.preferences?.updateAsOrder]);
|
|
3376
3485
|
const requestReport = useCallback5(
|
|
3377
3486
|
(section) => {
|
|
3378
3487
|
const id = `report-${section}-${today()}`;
|
|
@@ -3381,7 +3490,7 @@ function PanelApp({
|
|
|
3381
3490
|
return;
|
|
3382
3491
|
}
|
|
3383
3492
|
const card = {
|
|
3384
|
-
...emptyCard(
|
|
3493
|
+
...emptyCard(queueColumn(state.columns), state.areas[0]?.id ?? "general"),
|
|
3385
3494
|
id,
|
|
3386
3495
|
title: { en: UI.en.analysisRedoTitle[section], es: UI.es.analysisRedoTitle[section] },
|
|
3387
3496
|
body: { en: UI.en.analysisRedoBody[section], es: UI.es.analysisRedoBody[section] },
|
|
@@ -3394,12 +3503,63 @@ function PanelApp({
|
|
|
3394
3503
|
setState((current) => ({ ...current, updatedAt: today(), cards: [...current.cards, card] }));
|
|
3395
3504
|
setOpenCardId(id);
|
|
3396
3505
|
},
|
|
3397
|
-
[state.cards, state.areas]
|
|
3506
|
+
[state.cards, state.areas, state.columns]
|
|
3507
|
+
);
|
|
3508
|
+
const requestCard = useCallback5(
|
|
3509
|
+
(kind, subject, content) => {
|
|
3510
|
+
const waiting = pendingRequest(state.cards, kind, doneColumns, subject);
|
|
3511
|
+
if (waiting) {
|
|
3512
|
+
setOpenCardId(waiting.id);
|
|
3513
|
+
return;
|
|
3514
|
+
}
|
|
3515
|
+
const card = {
|
|
3516
|
+
...emptyCard(queueColumn(state.columns), state.areas[0]?.id ?? "general"),
|
|
3517
|
+
id: requestId(kind, today(), subject),
|
|
3518
|
+
title: content.title,
|
|
3519
|
+
body: content.body,
|
|
3520
|
+
priority: "medium",
|
|
3521
|
+
risk: "low",
|
|
3522
|
+
owner: "claude",
|
|
3523
|
+
order: true,
|
|
3524
|
+
intent: "do",
|
|
3525
|
+
checks: content.checks.map((text2) => ({ id: newId(), text: text2, done: false }))
|
|
3526
|
+
};
|
|
3527
|
+
setState((current) => ({ ...current, updatedAt: today(), cards: [...current.cards, card] }));
|
|
3528
|
+
setOpenCardId(card.id);
|
|
3529
|
+
},
|
|
3530
|
+
[state.cards, state.columns, state.areas, doneColumns]
|
|
3531
|
+
);
|
|
3532
|
+
const requestVersionBump = useCallback5(() => {
|
|
3533
|
+
requestCard("version-bump", void 0, {
|
|
3534
|
+
title: { en: UI.en.versionBumpTitle, es: UI.es.versionBumpTitle },
|
|
3535
|
+
body: { en: UI.en.versionBumpBody, es: UI.es.versionBumpBody },
|
|
3536
|
+
checks: UI.en.versionBumpChecks.map((en, i) => ({ en, es: UI.es.versionBumpChecks[i] }))
|
|
3537
|
+
});
|
|
3538
|
+
}, [requestCard]);
|
|
3539
|
+
const requestInstall = useCallback5(
|
|
3540
|
+
(tool, marketplace) => {
|
|
3541
|
+
const snippet = repositorySnippet(tool.id, marketplace).trimEnd();
|
|
3542
|
+
requestCard("install", tool.id, {
|
|
3543
|
+
title: { en: UI.en.installCardTitle(tool.name), es: UI.es.installCardTitle(tool.name) },
|
|
3544
|
+
body: {
|
|
3545
|
+
en: UI.en.installCardBody(tool.name, SETTINGS_PATH, snippet),
|
|
3546
|
+
es: UI.es.installCardBody(tool.name, SETTINGS_PATH, snippet)
|
|
3547
|
+
},
|
|
3548
|
+
checks: UI.en.installCardChecks.map((en, i) => ({ en, es: UI.es.installCardChecks[i] }))
|
|
3549
|
+
});
|
|
3550
|
+
},
|
|
3551
|
+
[requestCard]
|
|
3552
|
+
);
|
|
3553
|
+
const versionBumpWaiting = useMemo5(
|
|
3554
|
+
() => pendingRequest(state.cards, "version-bump", doneColumns),
|
|
3555
|
+
[state.cards, doneColumns]
|
|
3398
3556
|
);
|
|
3399
3557
|
const moveCard = useCallback5(
|
|
3400
3558
|
(move) => {
|
|
3401
3559
|
setState((current) => {
|
|
3402
|
-
const
|
|
3560
|
+
const done = new Set(current.columns.filter((column) => column.done).map((column) => column.id));
|
|
3561
|
+
const drawn = current.cards.filter((card) => matches(card) && !isArchived(card, done));
|
|
3562
|
+
const moved = applyMove(current.cards, move, drawn);
|
|
3403
3563
|
return {
|
|
3404
3564
|
...current,
|
|
3405
3565
|
updatedAt: today(),
|
|
@@ -3587,7 +3747,7 @@ function PanelApp({
|
|
|
3587
3747
|
throw Object.assign(new Error(saveError(response.status, data, body)), { conflict });
|
|
3588
3748
|
}
|
|
3589
3749
|
setSavedSnapshot(JSON.stringify(next));
|
|
3590
|
-
setState(next);
|
|
3750
|
+
setState((current) => current === state ? next : current);
|
|
3591
3751
|
setSave({
|
|
3592
3752
|
status: "saved",
|
|
3593
3753
|
at: (/* @__PURE__ */ new Date()).toLocaleTimeString(lang === "es" ? "es-ES" : "en-GB", {
|
|
@@ -3819,7 +3979,9 @@ function PanelApp({
|
|
|
3819
3979
|
failure: marketFailure,
|
|
3820
3980
|
lang,
|
|
3821
3981
|
ui,
|
|
3822
|
-
onRefresh: marketplaceEndpoint ? () => loadMarketplace(true) : void 0
|
|
3982
|
+
onRefresh: marketplaceEndpoint ? () => loadMarketplace(true) : void 0,
|
|
3983
|
+
onRequestInstall: saveEndpoint ? requestInstall : void 0,
|
|
3984
|
+
installRequested: (toolId) => Boolean(pendingRequest(state.cards, "install", doneColumns, toolId))
|
|
3823
3985
|
}
|
|
3824
3986
|
),
|
|
3825
3987
|
view === "analysis" && state.analysis && /* @__PURE__ */ jsx21(
|
|
@@ -3920,7 +4082,17 @@ function PanelApp({
|
|
|
3920
4082
|
},
|
|
3921
4083
|
area.id
|
|
3922
4084
|
))
|
|
3923
|
-
] })
|
|
4085
|
+
] }),
|
|
4086
|
+
saveEndpoint && /* @__PURE__ */ jsx21("div", { className: "pt-panel-row pt-requests", children: /* @__PURE__ */ jsx21(
|
|
4087
|
+
"button",
|
|
4088
|
+
{
|
|
4089
|
+
type: "button",
|
|
4090
|
+
className: "pt-btn pt-btn--sm",
|
|
4091
|
+
title: ui.versionBumpHow,
|
|
4092
|
+
onClick: requestVersionBump,
|
|
4093
|
+
children: versionBumpWaiting ? ui.versionBumpOpen : `\u2B06 ${ui.versionBumpAsk}`
|
|
4094
|
+
}
|
|
4095
|
+
) })
|
|
3924
4096
|
] }),
|
|
3925
4097
|
/* @__PURE__ */ jsx21(
|
|
3926
4098
|
Board,
|
|
@@ -4459,6 +4631,7 @@ export {
|
|
|
4459
4631
|
countCards,
|
|
4460
4632
|
cyberpunkTheme,
|
|
4461
4633
|
decided,
|
|
4634
|
+
dropIndex,
|
|
4462
4635
|
emptyCard,
|
|
4463
4636
|
explainBoard,
|
|
4464
4637
|
gateStatus,
|
|
@@ -4474,9 +4647,12 @@ export {
|
|
|
4474
4647
|
panelForm,
|
|
4475
4648
|
paneltirBuild,
|
|
4476
4649
|
paperForm,
|
|
4650
|
+
pendingRequest,
|
|
4651
|
+
queueColumn,
|
|
4477
4652
|
readBoard,
|
|
4478
4653
|
readStoredLang,
|
|
4479
4654
|
repositorySnippet,
|
|
4655
|
+
requestId,
|
|
4480
4656
|
resetGuide,
|
|
4481
4657
|
sparkPoints,
|
|
4482
4658
|
stampForColumn,
|
package/fingerprint.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paneltir",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"hash": "sha256:
|
|
5
|
-
"fileCount":
|
|
6
|
-
"templateHash": "sha256:
|
|
7
|
-
"generatedAt": "2026-09-
|
|
3
|
+
"version": "0.13.0",
|
|
4
|
+
"hash": "sha256:f3b5664bddf68f9560bd368b01184f97c733b9a5a3706507ae2219c6a41d9a1c",
|
|
5
|
+
"fileCount": 167,
|
|
6
|
+
"templateHash": "sha256:7c204bad5c7a8b329ad53ac51b5856438fe84735cea47543eb36b976edbb27da",
|
|
7
|
+
"generatedAt": "2026-09-26T09:08:52.803Z"
|
|
8
8
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paneltir",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "A React UI kit for admin panels: page frame, a board with touch-ready drag and drop, detail sheets, and a whole panel in one component. Structure and behaviour are fixed; colour and brand belong to each project.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "DFKlabs (https://DFKlabs.com)",
|
|
@@ -99,49 +99,35 @@
|
|
|
99
99
|
"node": ">=20"
|
|
100
100
|
},
|
|
101
101
|
"paneltirRelease": {
|
|
102
|
-
"version": "0.
|
|
103
|
-
"date": "2026-09-
|
|
102
|
+
"version": "0.13.0",
|
|
103
|
+
"date": "2026-09-26",
|
|
104
104
|
"entries": [
|
|
105
105
|
{
|
|
106
106
|
"kind": "feature",
|
|
107
107
|
"text": {
|
|
108
|
-
"en": "
|
|
109
|
-
"es": "
|
|
108
|
+
"en": "The board can ask for a version bump. \"Ask for a version bump\", under the filter row, writes an order card: every target the project ships moves up one version and one build, and they all end on the same pair — build 18, 1.0.18 becomes build 19, 1.0.19. The panel still runs nothing; the card is the request, and only one is open at a time.",
|
|
109
|
+
"es": "El tablero puede pedir una subida de versión. «Pedir subir versión», bajo la fila de filtros, escribe una tarjeta de orden: todos los targets del proyecto suben una versión y un build, y acaban en el mismo par — build 18, 1.0.18 pasa a build 19, 1.0.19. El panel sigue sin ejecutar nada; la tarjeta es la petición, y solo hay una abierta a la vez."
|
|
110
110
|
}
|
|
111
111
|
},
|
|
112
112
|
{
|
|
113
113
|
"kind": "feature",
|
|
114
114
|
"text": {
|
|
115
|
-
"en": "
|
|
116
|
-
"es": "
|
|
115
|
+
"en": "Installing a tool can be handed to Claude. The install sheet gains \"Add a card asking Claude to install it\", which writes an order carrying the exact settings snippet, and whose last step is the Tools screen reading Active. Offered for plugins not already active, one request per tool.",
|
|
116
|
+
"es": "Instalar una herramienta se le puede pasar a Claude. La hoja de instalar gana «Añadir una tarjeta para que Claude lo instale», que escribe una orden con el fragmento de ajustes exacto y cuyo último paso es que la pantalla de Herramientas diga Activo. Para plugins que no estén ya activos, una petición por herramienta."
|
|
117
117
|
}
|
|
118
118
|
},
|
|
119
119
|
{
|
|
120
120
|
"kind": "feature",
|
|
121
121
|
"text": {
|
|
122
|
-
"en": "
|
|
123
|
-
"es": "
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
"kind": "feature",
|
|
128
|
-
"text": {
|
|
129
|
-
"en": "`SituationRow` is exported, so a project building its own screen out of the components can draw the same strip.",
|
|
130
|
-
"es": "`SituationRow` se exporta, así que un proyecto que construye su propia pantalla con los componentes puede dibujar la misma franja."
|
|
131
|
-
}
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
"kind": "fix",
|
|
135
|
-
"text": {
|
|
136
|
-
"en": "The open decisions counter counts questions nobody has answered, not cards about deciding. Those are different numbers and the one that holds work up is the first.",
|
|
137
|
-
"es": "El contador de decisiones abiertas cuenta preguntas que nadie ha contestado, no tarjetas sobre decidir. Son números distintos y el que frena el trabajo es el primero."
|
|
122
|
+
"en": "`pendingRequest` and `requestId` are exported, so a project building its own screen can write the same requests with the same one-open-at-a-time rule.",
|
|
123
|
+
"es": "`pendingRequest` y `requestId` se exportan, así que un proyecto que construye su propia pantalla puede escribir las mismas peticiones con la misma regla de una abierta a la vez."
|
|
138
124
|
}
|
|
139
125
|
},
|
|
140
126
|
{
|
|
141
127
|
"kind": "chore",
|
|
142
128
|
"text": {
|
|
143
|
-
"en": "
|
|
144
|
-
"es": "
|
|
129
|
+
"en": "The skill a project keeps says how to carry out both cards, and `check:docs` fails if the version-bump rule stops reaching it.",
|
|
130
|
+
"es": "La skill que guarda cada proyecto explica cómo llevar a cabo las dos tarjetas, y `check:docs` falla si la regla de subir versión deja de llegarle."
|
|
145
131
|
}
|
|
146
132
|
}
|
|
147
133
|
]
|
|
@@ -59,6 +59,56 @@ lie in a file the owner reads on a phone.
|
|
|
59
59
|
**`risk`** — what it costs if it goes wrong, not how soon it is wanted. A
|
|
60
60
|
high-risk card gets the safer approach even when its intent says `fast`.
|
|
61
61
|
|
|
62
|
+
## A second problem, and a detail nobody wrote down
|
|
63
|
+
|
|
64
|
+
These two come up on almost every card, and neither is a reason to stop.
|
|
65
|
+
|
|
66
|
+
**A second problem found while doing a card.** Fix it in the same pass when it
|
|
67
|
+
is small, reversible, and inside what you were already touching. Anything
|
|
68
|
+
larger becomes a card: write it in `ideas` with a title, what you saw and why
|
|
69
|
+
it matters, then carry on with the card you were on and name the new card in
|
|
70
|
+
your reply. It is never a reason to stop and wait, and never something to fix
|
|
71
|
+
quietly inside an unrelated change — a queue stops being trusted when work
|
|
72
|
+
arrives in it that nobody asked for, and a finding stops existing when it was
|
|
73
|
+
only mentioned in a conversation that ended.
|
|
74
|
+
|
|
75
|
+
**A detail the card does not specify.** Decide it. That is the normal state of
|
|
76
|
+
a card rather than an omission: a card says what to build and rarely how, which
|
|
77
|
+
is why `do` says outright that *every detail not written down is yours to
|
|
78
|
+
choose* and an absent intent says to use judgement. Prefer the smaller, more
|
|
79
|
+
reversible option and say in one line what you chose and why.
|
|
80
|
+
|
|
81
|
+
Only `askme`, `explain` and `hold` mean stop, and `value: null` on a choice
|
|
82
|
+
holds back only the part of the work that actually depends on that answer —
|
|
83
|
+
never the whole card. Asking is for a question that outlives the conversation,
|
|
84
|
+
and it is asked on the board as a choice with `value: null`, which the bell
|
|
85
|
+
surfaces. Stopping to ask something you could have chosen costs the owner a
|
|
86
|
+
session; choosing it and saying so costs them one line.
|
|
87
|
+
|
|
88
|
+
## Cards the panel wrote for the owner
|
|
89
|
+
|
|
90
|
+
Some cards were not typed: a button wrote them, because the panel runs nothing
|
|
91
|
+
and can only record the ask. Their id says which button, and each is an order
|
|
92
|
+
with `intent: do` — carry it out like any other.
|
|
93
|
+
|
|
94
|
+
**`version-bump-<date>` — bump the version by one, on every target.** Find
|
|
95
|
+
every place this project carries a version or a build number: a web app's
|
|
96
|
+
`package.json`, an iOS app's `MARKETING_VERSION` and `CURRENT_PROJECT_VERSION`,
|
|
97
|
+
an Android app's `versionName` and `versionCode`, and anything else. Move the
|
|
98
|
+
version up one step and the build up one on each, so every target ends on the
|
|
99
|
+
same pair — build 18, version 1.0.18 becomes build 19, version 1.0.19. A target
|
|
100
|
+
with no build number moves its version only. If the targets already disagree,
|
|
101
|
+
bring them all to the highest plus one and say which were behind. Follow the
|
|
102
|
+
project's own release rules if it has any. Only one is ever open at a time:
|
|
103
|
+
the button opens the waiting card instead of writing a second, so two bumps in
|
|
104
|
+
a row are two separate asks, not a duplicate.
|
|
105
|
+
|
|
106
|
+
**`install-<tool>-<date>` — install a plugin in this repository.** The body
|
|
107
|
+
carries the exact snippet. Merge its two keys into `.claude/settings.json` —
|
|
108
|
+
merge, never replace: the file holds other settings — and commit. The last
|
|
109
|
+
step is the panel's Tools screen reading *Active*, which is how the install is
|
|
110
|
+
checked rather than assumed.
|
|
111
|
+
|
|
62
112
|
## Read the analysis before deciding anything
|
|
63
113
|
|
|
64
114
|
`analysis.choices` holds the questions the owner has already answered, grouped
|