feat(lanes): add Lanes section (skills install, housekeeping) to Settings (F4)
This commit is contained in:
@@ -61,6 +61,15 @@
|
|||||||
"success": "Hooks installed successfully",
|
"success": "Hooks installed successfully",
|
||||||
"failed": "Hook installation failed"
|
"failed": "Hook installation failed"
|
||||||
},
|
},
|
||||||
|
"lanesSection": {
|
||||||
|
"title": "Lanes",
|
||||||
|
"description": "Machine-wide housekeeping and one-time setup for the ship-feature-lane pipeline.",
|
||||||
|
"installSkill": "Install ship-feature-lane skill",
|
||||||
|
"installSkillSuccess": "Installed to {{path}}",
|
||||||
|
"runHousekeeping": "Run housekeeping",
|
||||||
|
"dryRun": "Dry run",
|
||||||
|
"gcResult": "Reaped {{reaped}} process(es), capped {{capped}} log(s)"
|
||||||
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"title": "Notifications",
|
"title": "Notifications",
|
||||||
"description": "Browser notifications for important events. Requires permission.",
|
"description": "Browser notifications for important events. Requires permission.",
|
||||||
|
|||||||
@@ -61,6 +61,15 @@
|
|||||||
"success": "Cài hooks thành công",
|
"success": "Cài hooks thành công",
|
||||||
"failed": "Cài hooks thất bại"
|
"failed": "Cài hooks thất bại"
|
||||||
},
|
},
|
||||||
|
"lanesSection": {
|
||||||
|
"title": "Lanes",
|
||||||
|
"description": "Bảo trì toàn máy và cài đặt một lần cho pipeline ship-feature-lane.",
|
||||||
|
"installSkill": "Cài skill ship-feature-lane",
|
||||||
|
"installSkillSuccess": "Đã cài vào {{path}}",
|
||||||
|
"runHousekeeping": "Chạy dọn dẹp",
|
||||||
|
"dryRun": "Chạy thử (dry run)",
|
||||||
|
"gcResult": "Đã dọn {{reaped}} tiến trình, cắt {{capped}} log"
|
||||||
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"title": "Thông báo",
|
"title": "Thông báo",
|
||||||
"description": "Thông báo trình duyệt cho các sự kiện quan trọng. Cần cấp quyền.",
|
"description": "Thông báo trình duyệt cho các sự kiện quan trọng. Cần cấp quyền.",
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ const SETTINGS_SECTIONS: {
|
|||||||
}[] = [
|
}[] = [
|
||||||
{ id: "pricing", labelKey: "pricing.title", Icon: DollarSign },
|
{ id: "pricing", labelKey: "pricing.title", Icon: DollarSign },
|
||||||
{ id: "hooks", labelKey: "hooks.title", Icon: Plug },
|
{ id: "hooks", labelKey: "hooks.title", Icon: Plug },
|
||||||
|
{ id: "lanes", labelKey: "lanesSection.title", Icon: GitBranch },
|
||||||
{ id: "claude-home", labelKey: "claudeHome.title", Icon: FolderOpen },
|
{ id: "claude-home", labelKey: "claudeHome.title", Icon: FolderOpen },
|
||||||
{ id: "import", labelKey: "import.title", fallback: "Import", Icon: History },
|
{ id: "import", labelKey: "import.title", fallback: "Import", Icon: History },
|
||||||
{
|
{
|
||||||
@@ -468,6 +469,7 @@ export function Settings() {
|
|||||||
}, []);
|
}, []);
|
||||||
const [abandonHours, setAbandonHours] = useState("24");
|
const [abandonHours, setAbandonHours] = useState("24");
|
||||||
const [purgeDays, setPurgeDays] = useState("90");
|
const [purgeDays, setPurgeDays] = useState("90");
|
||||||
|
const [gcDryRun, setGcDryRun] = useState(false);
|
||||||
const [claudeHome, setClaudeHomeState] = useState("");
|
const [claudeHome, setClaudeHomeState] = useState("");
|
||||||
const [claudeHomeInput, setClaudeHomeInput] = useState("");
|
const [claudeHomeInput, setClaudeHomeInput] = useState("");
|
||||||
const [claudeHomeSaving, setClaudeHomeSaving] = useState(false);
|
const [claudeHomeSaving, setClaudeHomeSaving] = useState(false);
|
||||||
@@ -727,6 +729,18 @@ export function Settings() {
|
|||||||
return res.ok ? t("hooks.success") : t("hooks.failed");
|
return res.ok ? t("hooks.success") : t("hooks.failed");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleSkillsInstall = () =>
|
||||||
|
runAction("skills-install", async () => {
|
||||||
|
const res = await api.settings.skillsInstall();
|
||||||
|
return t("lanesSection.installSkillSuccess", { path: res.path });
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleGc = () =>
|
||||||
|
runAction("gc", async () => {
|
||||||
|
const res = await api.settings.gc({ dryRun: gcDryRun });
|
||||||
|
return t("lanesSection.gcResult", { reaped: res.reaped.length, capped: res.capped.length });
|
||||||
|
});
|
||||||
|
|
||||||
const handleResetPricing = () =>
|
const handleResetPricing = () =>
|
||||||
runAction("reset-pricing", async () => {
|
runAction("reset-pricing", async () => {
|
||||||
const res = await api.settings.resetPricing();
|
const res = await api.settings.resetPricing();
|
||||||
@@ -1357,6 +1371,58 @@ export function Settings() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* ─── LANES ─── */}
|
||||||
|
<section id="lanes" className="scroll-mt-24">
|
||||||
|
<h3 className="text-sm font-medium text-fg-secondary flex items-center gap-2 mb-1">
|
||||||
|
<GitBranch className="w-4 h-4 text-fg-muted" />
|
||||||
|
{t("lanesSection.title")}
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs text-fg-muted mb-4">{t("lanesSection.description")}</p>
|
||||||
|
|
||||||
|
<div className="card p-5 space-y-4">
|
||||||
|
<div className="flex items-center justify-between flex-wrap gap-3">
|
||||||
|
<span className="text-xs text-fg-secondary">{t("lanesSection.installSkill")}</span>
|
||||||
|
<button
|
||||||
|
onClick={handleSkillsInstall}
|
||||||
|
disabled={actionLoading !== null}
|
||||||
|
className="btn-ghost text-xs disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{actionLoading === "skills-install" ? (
|
||||||
|
<RefreshCw className="w-3.5 h-3.5 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<RotateCcw className="w-3.5 h-3.5" />
|
||||||
|
)}
|
||||||
|
{t("lanesSection.installSkill")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{actionBanner(["skills-install"])}
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between flex-wrap gap-3">
|
||||||
|
<label className="flex items-center gap-2 text-xs text-fg-secondary">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={gcDryRun}
|
||||||
|
onChange={(e) => setGcDryRun(e.target.checked)}
|
||||||
|
/>
|
||||||
|
{t("lanesSection.dryRun")}
|
||||||
|
</label>
|
||||||
|
<button
|
||||||
|
onClick={handleGc}
|
||||||
|
disabled={actionLoading !== null}
|
||||||
|
className="btn-ghost text-xs disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{actionLoading === "gc" ? (
|
||||||
|
<RefreshCw className="w-3.5 h-3.5 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<RotateCcw className="w-3.5 h-3.5" />
|
||||||
|
)}
|
||||||
|
{t("lanesSection.runHousekeeping")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{actionBanner(["gc"])}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
{/* ─── CLAUDE HOME ─── */}
|
{/* ─── CLAUDE HOME ─── */}
|
||||||
<section id="claude-home" className="scroll-mt-24">
|
<section id="claude-home" className="scroll-mt-24">
|
||||||
<h3 className="text-sm font-medium text-fg-secondary flex items-center gap-2 mb-1">
|
<h3 className="text-sm font-medium text-fg-secondary flex items-center gap-2 mb-1">
|
||||||
|
|||||||
@@ -7522,6 +7522,44 @@ exports[`screen snapshots > Settings 1`] = `
|
|||||||
</svg>
|
</svg>
|
||||||
Hook Configuration
|
Hook Configuration
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
class="inline-flex items-center gap-1.5 text-xs whitespace-nowrap px-2.5 py-1.5 rounded-lg border transition-colors flex-shrink-0 border-border text-fg-secondary hover:text-fg-primary hover:bg-surface-3"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="lucide lucide-git-branch w-3.5 h-3.5"
|
||||||
|
fill="none"
|
||||||
|
height="24"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<line
|
||||||
|
x1="6"
|
||||||
|
x2="6"
|
||||||
|
y1="3"
|
||||||
|
y2="15"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx="18"
|
||||||
|
cy="6"
|
||||||
|
r="3"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx="6"
|
||||||
|
cy="18"
|
||||||
|
r="3"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M18 9a9 9 0 0 1-9 9"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Lanes
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
class="inline-flex items-center gap-1.5 text-xs whitespace-nowrap px-2.5 py-1.5 rounded-lg border transition-colors flex-shrink-0 border-border text-fg-secondary hover:text-fg-primary hover:bg-surface-3"
|
class="inline-flex items-center gap-1.5 text-xs whitespace-nowrap px-2.5 py-1.5 rounded-lg border transition-colors flex-shrink-0 border-border text-fg-secondary hover:text-fg-primary hover:bg-surface-3"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -8127,6 +8165,126 @@ exports[`screen snapshots > Settings 1`] = `
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
<section
|
||||||
|
class="scroll-mt-24"
|
||||||
|
id="lanes"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="text-sm font-medium text-fg-secondary flex items-center gap-2 mb-1"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="lucide lucide-git-branch w-4 h-4 text-fg-muted"
|
||||||
|
fill="none"
|
||||||
|
height="24"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<line
|
||||||
|
x1="6"
|
||||||
|
x2="6"
|
||||||
|
y1="3"
|
||||||
|
y2="15"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx="18"
|
||||||
|
cy="6"
|
||||||
|
r="3"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx="6"
|
||||||
|
cy="18"
|
||||||
|
r="3"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M18 9a9 9 0 0 1-9 9"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Lanes
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
class="text-xs text-fg-muted mb-4"
|
||||||
|
>
|
||||||
|
Machine-wide housekeeping and one-time setup for the ship-feature-lane pipeline.
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="card p-5 space-y-4"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-between flex-wrap gap-3"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="text-xs text-fg-secondary"
|
||||||
|
>
|
||||||
|
Install ship-feature-lane skill
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
class="btn-ghost text-xs disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="lucide lucide-rotate-ccw w-3.5 h-3.5"
|
||||||
|
fill="none"
|
||||||
|
height="24"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M3 3v5h5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Install ship-feature-lane skill
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-between flex-wrap gap-3"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="flex items-center gap-2 text-xs text-fg-secondary"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
Dry run
|
||||||
|
</label>
|
||||||
|
<button
|
||||||
|
class="btn-ghost text-xs disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="lucide lucide-rotate-ccw w-3.5 h-3.5"
|
||||||
|
fill="none"
|
||||||
|
height="24"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M3 3v5h5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Run housekeeping
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
<section
|
<section
|
||||||
class="scroll-mt-24"
|
class="scroll-mt-24"
|
||||||
id="claude-home"
|
id="claude-home"
|
||||||
|
|||||||
Reference in New Issue
Block a user