Răsfoiți Sursa

Merge branch 'a1-labels' of bajdev/mpckit into main

bajdev 5 ore în urmă
părinte
comite
e03027aa6d

+ 2 - 2
frontend/bindings/git.monoexp.com/bajdev/mpckit/internal/app/models.ts

@@ -59,7 +59,7 @@ export interface Pad {
     "slot": number;
 
     /**
-     * "A01" .. "H16"
+     * "A1" .. "H16"
      */
     "label": string;
 
@@ -105,7 +105,7 @@ export interface Sequence {
     "slot": number;
 
     /**
-     * slot label, "A01" .. "H16"
+     * slot label, "A1" .. "H16"
      */
     "label": string;
     "name": string;

+ 1 - 1
frontend/src/lib/SlotGrid.svelte

@@ -1,7 +1,7 @@
 <script lang="ts" module>
   export type Cell = {
     slot: number;
-    label: string; // "A01"
+    label: string; // "A1"
     name: string; // sample or sequence name; empty for an empty slot
     meta: string; // small line under the name
     aria: string;

+ 2 - 3
frontend/src/lib/slots.ts

@@ -3,10 +3,9 @@
 export const BANKS = "ABCDEFGH";
 export const PER_BANK = 16;
 
-/** "A01" for slot 0, "B02" for slot 17. Matches Go's app.Label. */
+/** "A1" for slot 0, "B2" for slot 17. Matches Go's project.SlotName. */
 export function slotLabel(slot: number): string {
-  const n = (slot % PER_BANK) + 1;
-  return BANKS[Math.floor(slot / PER_BANK)] + (n < 10 ? "0" + n : String(n));
+  return BANKS[Math.floor(slot / PER_BANK)] + ((slot % PER_BANK) + 1);
 }
 
 /**

+ 5 - 11
internal/app/app.go

@@ -30,7 +30,7 @@ const DefaultName = "NewKit"
 // Pad is one pad as the frontend shows it.
 type Pad struct {
 	Slot      int    `json:"slot"`
-	Label     string `json:"label"` // "A01" .. "H16"
+	Label     string `json:"label"` // "A1" .. "H16"
 	Name      string `json:"name"`  // sample name; empty for an empty pad
 	File      string `json:"file"`  // pool file name
 	Coarse    int    `json:"coarse"`
@@ -53,7 +53,7 @@ type Pad struct {
 // Sequence is one existing sequence.
 type Sequence struct {
 	Slot     int     `json:"slot"`
-	Label    string  `json:"label"` // slot label, "A01" .. "H16"
+	Label    string  `json:"label"` // slot label, "A1" .. "H16"
 	Name     string  `json:"name"`
 	BPM      float64 `json:"bpm"`
 	Bars     int     `json:"bars"`
@@ -206,12 +206,6 @@ func (s *Service) About() About {
 	}
 }
 
-// Label returns the label the UI and the device use for a pad or sequence
-// slot: bank letter and two-digit pad number, e.g. 0 -> "A01", 17 -> "B02".
-func Label(slot int) string {
-	return fmt.Sprintf("%c%02d", 'A'+slot/16, slot%16+1)
-}
-
 func (s *Service) state() State {
 	p := s.p
 	st := State{
@@ -243,10 +237,10 @@ func (s *Service) state() State {
 		v, err := p.Pad(slot)
 		if err != nil {
 			// A loaded file with an odd pad; show it as empty.
-			st.Pads = append(st.Pads, Pad{Slot: slot, Label: Label(slot)})
+			st.Pads = append(st.Pads, Pad{Slot: slot, Label: project.SlotName(slot)})
 			continue
 		}
-		pad := Pad{Slot: slot, Label: Label(slot), Name: v.SampleName, File: v.File,
+		pad := Pad{Slot: slot, Label: project.SlotName(slot), Name: v.SampleName, File: v.File,
 			Coarse: v.Coarse, Fine: v.Fine, MuteGroup: v.MuteGroup, Start: v.Start, End: v.End}
 		if !v.Empty() {
 			pad.HasNotes = p.PadHasNotes(slot)
@@ -259,7 +253,7 @@ func (s *Service) state() State {
 		st.Pads = append(st.Pads, pad)
 	}
 	for _, q := range p.Sequences() {
-		st.Sequences = append(st.Sequences, Sequence{Slot: q.Slot, Label: Label(q.Slot),
+		st.Sequences = append(st.Sequences, Sequence{Slot: q.Slot, Label: project.SlotName(q.Slot),
 			Name: q.Name, BPM: q.BPM, Bars: q.Bars, HasNotes: q.HasNotes})
 	}
 	st.SequenceCount = len(st.Sequences)

+ 10 - 10
internal/app/app_test.go

@@ -53,7 +53,7 @@ func TestNewState(t *testing.T) {
 	if st.Name != DefaultName || st.Dirty || st.SampleCount != 0 {
 		t.Errorf("state %q dirty %v samples %d", st.Name, st.Dirty, st.SampleCount)
 	}
-	if len(st.Pads) != 128 || st.Pads[0].Label != "A01" || st.Pads[17].Label != "B02" || st.Pads[127].Label != "H16" {
+	if len(st.Pads) != 128 || st.Pads[0].Label != "A1" || st.Pads[17].Label != "B2" || st.Pads[127].Label != "H16" {
 		t.Fatalf("pads: %d", len(st.Pads))
 	}
 	for _, p := range st.Pads {
@@ -61,7 +61,7 @@ func TestNewState(t *testing.T) {
 			t.Fatalf("pad %s not empty", p.Label)
 		}
 	}
-	if st.SequenceCount != 1 || st.Sequences[0] != (Sequence{Slot: 0, Label: "A01", Name: "Sequence 01", BPM: 128, Bars: 2}) {
+	if st.SequenceCount != 1 || st.Sequences[0] != (Sequence{Slot: 0, Label: "A1", Name: "Sequence 01", BPM: 128, Bars: 2}) {
 		t.Errorf("sequences %+v", st.Sequences)
 	}
 	if st.Limits.MaxMuteGroup != 16 || st.Limits.MinBPM != 20 || st.Limits.MaxBPM != 999 || st.Limits.MinCoarse != -24 {
@@ -76,7 +76,7 @@ func TestStateJSON(t *testing.T) {
 	s, _ := newService()
 	b, err := json.Marshal(s.State())
 	must(t, err)
-	for _, key := range []string{`"name":"NewKit"`, `"pads":[{"slot":0,"label":"A01"`, `"sequences":[{"slot":0`, `"limits":{`, `"muteGroup":0`, `"audio":""`} {
+	for _, key := range []string{`"name":"NewKit"`, `"pads":[{"slot":0,"label":"A1"`, `"sequences":[{"slot":0`, `"limits":{`, `"muteGroup":0`, `"audio":""`} {
 		if !strings.Contains(string(b), key) {
 			t.Errorf("JSON lacks %s", key)
 		}
@@ -396,7 +396,7 @@ func TestMoveCopySwap(t *testing.T) {
 	st, err = s.MovePad(40, 0)
 	must(t, err)
 	if st.Pads[40].File != "" || st.Pads[0].File != "Linn 9000 kick001.wav" || st.Pads[0].Coarse != 0 {
-		t.Errorf("move: A01 %+v", st.Pads[0])
+		t.Errorf("move: A1 %+v", st.Pads[0])
 	}
 	if _, err := s.MovePad(0, 128); err == nil {
 		t.Error("move to slot 128 accepted")
@@ -420,14 +420,14 @@ func TestLoad(t *testing.T) {
 	if st.Name != "DemoFile" || st.Dirty || st.SampleCount != 6 || len(res.Warnings) != 0 || res.Path != d.project {
 		t.Errorf("load: %q dirty %v samples %d warnings %v", st.Name, st.Dirty, st.SampleCount, res.Warnings)
 	}
-	// DemoFile has notes on A01-A04 only.
+	// DemoFile has notes on A1-A4 only.
 	for i, p := range st.Pads {
 		if p.HasNotes != (i < 4) {
 			t.Errorf("%s hasNotes %v", p.Label, p.HasNotes)
 		}
 	}
 	if !strings.HasPrefix(st.Pads[0].Audio, "/audio/0?k=") || st.Pads[0].End != 44099 {
-		t.Errorf("A01 %+v", st.Pads[0])
+		t.Errorf("A1 %+v", st.Pads[0])
 	}
 	if st.SequenceCount != 2 || st.Sequences[0].Name != "Intro" || !st.Sequences[0].HasNotes {
 		t.Errorf("sequences %+v", st.Sequences)
@@ -444,7 +444,7 @@ func TestSequences(t *testing.T) {
 	s, _ := newService()
 	st, err := s.AddSequence(20)
 	must(t, err)
-	if st.SequenceCount != 2 || st.Sequences[1] != (Sequence{Slot: 20, Label: "B05", Name: "Sequence 21", BPM: 120, Bars: 2}) {
+	if st.SequenceCount != 2 || st.Sequences[1] != (Sequence{Slot: 20, Label: "B5", Name: "Sequence 21", BPM: 120, Bars: 2}) {
 		t.Errorf("add: %+v", st.Sequences)
 	}
 	st, err = s.SetSequence(20, " Drop ", 140.5, 16)
@@ -532,10 +532,10 @@ func TestMissingFiles(t *testing.T) {
 	}
 	a2 := st.Pads[1]
 	if !a2.Missing || a2.Audio != "" || !strings.HasSuffix(a2.Source, "80s-Snr-Dreamer SA.wav") {
-		t.Errorf("A02 %+v", a2)
+		t.Errorf("A2 %+v", a2)
 	}
 	if st.Pads[0].Missing || st.Pads[0].Audio == "" {
-		t.Error("A01 marked missing")
+		t.Error("A1 marked missing")
 	}
 
 	// Export names the missing file and writes nothing.
@@ -590,6 +590,6 @@ func TestLinkedFileIsMissing(t *testing.T) {
 	res, err := s.LoadPath(path)
 	must(t, err)
 	if st := res.State; st.MissingCount != 1 || !st.Pads[1].Missing {
-		t.Errorf("missing count %d, A02 missing %v; want 1, true", st.MissingCount, st.Pads[1].Missing)
+		t.Errorf("missing count %d, A2 missing %v; want 1, true", st.MissingCount, st.Pads[1].Missing)
 	}
 }

+ 42 - 0
pull-requests/a1-labels/one-pad-label-format-a1.md

@@ -0,0 +1,42 @@
+# One pad label format: "A1"
+
+Branch: `a1-labels` → `main`
+
+PR 7 of the review fixes plan (`docs/review-fixes-plan.md`), part of the 0.5.3 series; the version does not change in this PR.
+
+## What changed
+
+**"A1" labels everywhere (M6).** The app showed pads and sequence slots as "A01", "B02"; the CLI and `project.SlotName` used "A1", "B2". As decided, it is "A1" everywhere now: bank letter, then pad number without a leading zero.
+- **Go:** `app.Label` is removed, and the app's state uses `project.SlotName`, the one Go implementation. That covers the pad grid, pad panel, sequence grid and confirm dialogs, which all take their labels from the state.
+- **Frontend:** `slotLabel` in `frontend/src/lib/slots.ts` now gives "A1". The sequence panel uses it for the slot shown in its header.
+- **Comments:** the ones that said "A01" are fixed in `app.go`, `SlotGrid.svelte`, `slots.ts`, and the regenerated `models.ts`.
+- **Unchanged:**
+  - sequence names such as "Sequence 01", which is the device's own naming, not a slot label;
+  - the CLI, which already used "A1".
+- **Tests:** the app tests now expect "A1", "B2", "B5" and "H16".
+
+## Notes
+
+- **The history stays as it was.** `docs/checkpoint-b.md` keeps "A01" in its steps and results: it records the device test as run with that UI. The earlier PR records are left alone too.
+- **Layout:** labels are one character shorter for pads 1–9 and the same length otherwise, so nothing can get wider. Please confirm the fit on the laptop (step 4).
+
+## Testing
+
+**On the server, every command with `GOTOOLCHAIN=go1.27.4`:**
+- `go test ./...` passes.
+- `go vet ./...` (also with `GOOS=windows`) and `staticcheck ./...` are clean.
+- `govulncheck ./...`: "No vulnerabilities found."
+- `npm install`, `npm run check` (0 errors, 0 warnings) and `npm run build` pass. The lockfile was not committed.
+- `wails3 build` builds, and the app starts under Xvfb.
+- `go run ./cmd/mpckit-cli -load testdata/device/DemoFile.xpj -name LabelCheck -out <tmp> -mute B2=3` prints `A1` … `B2` in its summary.
+
+**On the laptop (PowerShell, in the repo):**
+1. `git fetch; git checkout a1-labels`
+2. `cd frontend; npm run check; cd ..`: 0 errors, 0 warnings.
+3. `wails3 dev`: load `testdata\device\DemoFile.xpj`.
+   - **Pads tab:** the grid shows A1–A4 and the pad panel shows "A1".
+   - **Sequences tab:** the grid and panel show "A1" and "A2", and the sequence names are still "Intro" and "Sequence 02".
+4. At the default window size, nothing scrolls and nothing is cut off on either tab.
+5. Drag A1 onto A5. The notes warning names "A1" and "A5". Then use Move to pad / Copy to pad: the picker and its confirm dialog use "A1" style too.
+6. Add a sequence in an empty slot, such as B5. Its panel shows "B5", and its default name is "Sequence 21".
+7. `go run ./cmd/mpckit-cli -load testdata\device\DemoFile.xpj -name LabelCheck -out $env:TEMP`: the summary lists `A1` … `B2`. Then delete `LabelCheck.xpj` and `LabelCheck_[ProjectData]` from `$env:TEMP`.