4 Commits

Author SHA1 Message Date
itsamejms 41751a1a58 flipping the vertical chord diagrams for guitar 2026-07-14 09:55:57 +01:00
itsamejms ce22561813 minor small screen optimizations 2026-07-13 22:13:40 +01:00
itsamejms 436522f3b4 Merge branch 'sprint-jamguide-piano' 2026-07-13 21:51:24 +01:00
vadimwit 072920ff0e docs+config: never-delete policy — CLAUDE.md hard rule + .claude/settings.json deny list
Claude must never execute destructive/irreversible commands (rm, git branch
-d/-D, git push --delete, reset --hard, force-push, DROP, etc.) — it proposes
them for the user to run. Enforced behaviorally in CLAUDE.md (authoritative)
and as permissions.deny rules in .claude/settings.json (defense-in-depth).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 21:36:02 +01:00
7 changed files with 77 additions and 24 deletions
+35
View File
@@ -0,0 +1,35 @@
{
"$comment": "Team-shared restricted options. Destructive/irreversible commands are DENIED so Claude proposes them for the user to run instead of executing them. Authoritative policy: see the 'Destructive operations — NEVER delete' section of CLAUDE.md.",
"permissions": {
"deny": [
"Bash(rm:*)",
"Bash(rmdir:*)",
"Bash(git branch -d:*)",
"Bash(git branch -D:*)",
"Bash(git branch --delete:*)",
"Bash(git push --delete:*)",
"Bash(git push -d:*)",
"Bash(git push origin --delete:*)",
"Bash(git push github --delete:*)",
"Bash(git push origin -d:*)",
"Bash(git push github -d:*)",
"Bash(git tag -d:*)",
"Bash(git tag --delete:*)",
"Bash(git remote remove:*)",
"Bash(git remote rm:*)",
"Bash(git reset --hard:*)",
"Bash(git clean -f:*)",
"Bash(git clean -d:*)",
"Bash(git clean -x:*)",
"Bash(git push --force:*)",
"Bash(git push -f:*)",
"Bash(git push --force-with-lease:*)",
"Bash(git checkout --:*)",
"PowerShell(Remove-Item:*)",
"PowerShell(rm:*)",
"PowerShell(del:*)",
"PowerShell(rmdir:*)",
"PowerShell(Clear-Content:*)"
]
}
}
+18
View File
@@ -2,6 +2,24 @@
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Destructive operations — NEVER delete (hard rule)
**Claude must never EXECUTE a destructive or irreversible command. Always ask, and hand the user the exact command(s) to run themselves.**
This covers (non-exhaustively):
- Deleting files/directories: `rm`, `rm -rf`, `rmdir`, `del`, `Remove-Item`.
- Deleting branches: `git branch -d` / `-D`, `git push --delete`, `git push <remote> :branch`.
- Deleting tags/remotes: `git tag -d`, `git remote remove` / `rm`.
- Discarding work: `git reset --hard`, `git checkout -- <path>`, `git clean -f`.
- Force-pushing: `git push -f` / `--force` / `--force-with-lease`.
- Dropping data: `DROP`, `TRUNCATE`, destructive migrations.
Instead: print the command(s) in a fenced block with a one-line note on what each does and what it affects, and let the **user run them**. Never run them yourself, even when the desired outcome is clear — `rm` and `-d` are **prompted, never executed**.
Leave regular branches alone (`main`, the active sprint branch) unless the user explicitly names them. Before calling any branch "stale", prove containment (`git branch --merged`, 0 unique commits) and report that evidence — do not act on it.
These are also enforced as `permissions.deny` rules in `.claude/settings.json` (defense-in-depth), but this behavioral rule is authoritative and covers cases the patterns can't.
## Commands
```bash
+5 -5
View File
@@ -589,7 +589,7 @@ export default function App() {
}
return (
<div className={`min-h-screen bg-surface text-white p-3${jamView ? ' xl:h-screen xl:overflow-hidden xl:flex xl:flex-col' : ''}`}>
<div className={`min-h-screen bg-surface text-white p-3 max-sm:p-2${jamView ? ' xl:h-screen xl:overflow-hidden xl:flex xl:flex-col' : ''}`}>
{/* ── Header ── */}
<header className="mb-2 flex items-center justify-between gap-2 flex-wrap">
@@ -628,7 +628,7 @@ export default function App() {
</header>
{/* ── Controls bar ── */}
<div className="mb-2 flex flex-wrap gap-2 items-center p-2 bg-panel border border-border rounded-xl">
<div className="mb-2 relative flex flex-wrap gap-2 items-center p-2 max-sm:p-1.5 bg-panel border border-border rounded-xl">
{/* Instrument select */}
<div className="relative">
@@ -731,7 +731,7 @@ export default function App() {
)}
{/* ── Jam roulette (L-60, jam-roulette.md §1.1/§1.2) ── */}
<div className="relative ml-auto" ref={rouletteRef}>
<div className="static sm:relative ml-auto" ref={rouletteRef}>
<button
type="button"
onClick={() => setRouletteMenuOpen(o => !o)}
@@ -910,7 +910,7 @@ export default function App() {
<span>{showDebug ? '▲' : '▼'}</span>
</button>
{showDebug && (
<div className="border-t border-border p-4">
<div className="border-t border-border p-4 max-sm:p-3">
<Suspense fallback={null}>
<DebugView
chroma={debugChroma}
@@ -937,7 +937,7 @@ export default function App() {
<span>{showDrumView ? '▲' : '▼'}</span>
</button>
{showDrumView && (
<div className="border-t border-border p-4">
<div className="border-t border-border p-4 max-sm:p-3">
<Suspense fallback={null}>
<DrumView waveform={debugWaveform} bpm={bpm} />
</Suspense>
+5 -5
View File
@@ -757,13 +757,13 @@ export default function ChordDetailModal({ chord, onClose, onChordClick, keyInfo
return (
<div
className="fixed inset-0 z-50 flex items-start justify-center bg-black/70 backdrop-blur-sm p-4 overflow-y-auto"
className="fixed inset-0 z-50 flex items-start justify-center bg-black/70 backdrop-blur-sm p-4 max-sm:p-2 overflow-y-auto"
onClick={e => { if (e.target === e.currentTarget) onClose() }}
>
<div className="w-full max-w-3xl bg-panel border border-border rounded-2xl shadow-2xl mt-8 mb-8">
<div className="w-full max-w-3xl bg-panel border border-border rounded-2xl shadow-2xl mt-8 mb-8 max-sm:my-2">
{/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b border-border">
<div className="flex items-center justify-between px-6 py-4 max-sm:px-4 max-sm:py-3 border-b border-border">
<div>
<h2 className="text-3xl font-black text-accent leading-none">{chord}</h2>
<p className="text-xs text-gray-500 mt-0.5">{typeName} chord · tap a voicing to study it</p>
@@ -778,7 +778,7 @@ export default function ChordDetailModal({ chord, onClose, onChordClick, keyInfo
</div>
{/* Tab bar */}
<div className="flex gap-1 px-6 pt-4 overflow-x-auto">
<div className="flex gap-1 px-6 pt-4 max-sm:px-4 max-sm:pt-3 overflow-x-auto">
{[
{ key: 'guitar', label: '🎸 Guitar' },
{ key: 'piano', label: '🎹 Piano' },
@@ -800,7 +800,7 @@ export default function ChordDetailModal({ chord, onClose, onChordClick, keyInfo
</div>
{/* Content */}
<div className="px-6 py-5">
<div className="px-6 py-5 max-sm:px-4 max-sm:py-4">
{tab === 'guitar' && <GuitarTab chordName={chord} />}
{tab === 'piano' && <PianoTab chordName={chord} />}
{tab === 'theory' && <TheoryTab chordName={chord} />}
+8 -8
View File
@@ -24,9 +24,9 @@
// Standard tuning open-string pitch classes, indexed low-E (0) → high-E (5).
const OPEN_PCS = [4, 9, 2, 7, 11, 4] // E A D G B E
// We render strings top→bottom as high-E first (matches Fretboard.jsx idiom),
// so display index 0 = high E, 5 = low E. Data arrays are low-E first, so the
// data index for display row `di` is `5 - di`.
// We render strings left→right as low-E first (chord-diagram convention:
// 6th string on the left, 1st on the right). Data arrays are also low-E first,
// so display row index == data index.
const ACCENT = '#a855f7' // chord-tone tier (root highlight)
const DOT = '#e5e7eb' // non-root finger dots (light gray, AA on dark board)
@@ -114,10 +114,10 @@ function buildRows(resolved) {
// Root pitch class for colouring.
const rootPc = resolved.rootPc
// Convert to display rows (high-E first → reverse of low-E-first).
// Convert to display rows (low-E first — display index == data index).
const rows = []
for (let di = 0; di < NUM_STRINGS; di++) {
const dataIdx = NUM_STRINGS - 1 - di
const dataIdx = di
const f = absLowE[dataIdx]
const stringPc = (OPEN_PCS[dataIdx] + (typeof f === 'number' ? f : 0)) % 12
const isRoot = typeof f === 'number' && f >= 0 && stringPc === rootPc
@@ -153,7 +153,7 @@ export default function ChordDiagram({
const svgW = padL + gridW + padR
const svgH = padT + gridH + padB
const stringX = si => padL + si * sw // si: 0 = high E (left) … 5 = low E
const stringX = si => padL + si * sw // si: 0 = low E (left) … 5 = high E
const fretY = fi => padT + fi * cell // fi: 0 = top line … NUM_FRETS
if (!built) {
@@ -235,14 +235,14 @@ export default function ChordDiagram({
x2={stringX(si)}
y2={fretY(NUM_FRETS)}
stroke={STRING_COL}
strokeWidth={(si >= 4 ? 1.4 : si >= 2 ? 1.1 : 0.8) * scale}
strokeWidth={(si < 2 ? 1.4 : si < 4 ? 1.1 : 0.8) * scale}
/>
))}
{/* Per-string markers: mute ✕ / open ○ above the nut, dots on the grid */}
{rows.map((row, si) => {
const x = stringX(si)
const dataIdx = NUM_STRINGS - 1 - si
const dataIdx = si
const finger = fingers ? fingers[dataIdx] : 0
// Muted string → ✕ above the board.
+4 -4
View File
@@ -856,7 +856,7 @@ export function KnowledgeDock({ keyInfo, chordHistory = [], currentChord, onChor
<div className="border-t border-border flex flex-col" style={{ height: '70vh' }}>
{/* ── Section nav (Knowledge Center pills, D-20 §1) ── */}
<div className="flex items-center gap-1 px-4 py-2 border-b border-border overflow-x-auto">
<div className="flex items-center gap-1 px-4 py-2 max-sm:px-3 border-b border-border overflow-x-auto">
{SECTIONS.map(s => {
const active = section === s.id
return (
@@ -879,7 +879,7 @@ export function KnowledgeDock({ keyInfo, chordHistory = [], currentChord, onChor
{/* ── Explore — KB progression browser + famous progressions ── */}
{section === 'explore' && (
<div className="flex-1 min-h-0 p-4 overflow-auto">
<div className="flex-1 min-h-0 p-4 max-sm:p-3 overflow-auto">
<ExploreSection
keyInfo={keyInfo}
levels={levels}
@@ -891,7 +891,7 @@ export function KnowledgeDock({ keyInfo, chordHistory = [], currentChord, onChor
{/* ── Voicings — picker (follows live chord) → VoicingBrowser ── */}
{section === 'voicings' && (
<div className="flex-1 min-h-0 p-4 overflow-auto">
<div className="flex-1 min-h-0 p-4 max-sm:p-3 overflow-auto">
<VoicingsSection
keyInfo={keyInfo}
chordHistory={chordHistory}
@@ -903,7 +903,7 @@ export function KnowledgeDock({ keyInfo, chordHistory = [], currentChord, onChor
{/* ── Licks & Techniques — per-style LickCard grid ── */}
{section === 'licks' && (
<div className="flex-1 min-h-0 p-4 overflow-auto">
<div className="flex-1 min-h-0 p-4 max-sm:p-3 overflow-auto">
<LicksSection styles={styles} levels={levels} onToggleLevel={toggleLevel} />
</div>
)}
+2 -2
View File
@@ -156,10 +156,10 @@ function SubRow({ sub, instrument, onChordClick }) {
const tag = CATEGORY_TAG[sub.category] ?? sub.category
return (
<div
className={`flex items-center gap-3 rounded-lg border border-border bg-border/30 p-2 ${ROW_MIN_H[instrument] ?? ROW_MIN_H.guitar}`}
className={`flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3 rounded-lg border border-border bg-border/30 p-2 ${ROW_MIN_H[instrument] ?? ROW_MIN_H.guitar}`}
>
{/* Left identity block (~180px) */}
<div className="flex w-[180px] shrink-0 flex-col gap-1">
<div className="flex w-full sm:w-[180px] shrink-0 flex-col gap-1">
<div className="flex items-center gap-1.5">
<button
type="button"