Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
493f54c
feat: Slider UI 컴포넌트 추가
seonghobae Sep 7, 2026
1173519
chore: Slider 컴포넌트에 JSDoc 주석 추가
seonghobae Sep 7, 2026
c32ae27
chore: Slider 컴포넌트에 JSDoc 주석 추가
seonghobae Sep 7, 2026
7771b31
Trigger CI retry
seonghobae Sep 7, 2026
6dfb96b
Trigger CI retry
seonghobae Sep 7, 2026
7070149
chore: trigger CI retry
seonghobae Sep 8, 2026
4252e88
chore: trigger CI retry
seonghobae Sep 8, 2026
35abb4a
chore: trigger CI retry
seonghobae Sep 8, 2026
467446c
chore: trigger CI retry
seonghobae Sep 8, 2026
88f34b3
chore: trigger CI retry
seonghobae Sep 8, 2026
ba07e4a
test(slider): lock vertical rendered layout
seonghobae Sep 8, 2026
a87130a
test(slider): narrow focus-visible contract claim
seonghobae Sep 8, 2026
6618a84
chore: trigger CI retry
seonghobae Sep 8, 2026
6c800c6
chore: trigger CI retry
seonghobae Sep 8, 2026
b70402b
test(ui): assert slider keyboard focus routing
seonghobae Sep 8, 2026
0ce9d52
test(ui): assert slider keyboard focus routing
seonghobae Sep 8, 2026
21a84ec
test(ui): assert slider keyboard focus routing
seonghobae Sep 8, 2026
f10c72c
fix(ui): align Slider Storybook types with Vite framework
seonghobae Sep 9, 2026
87b2b31
fix(ui): align Slider Storybook types with Vite framework
seonghobae Sep 9, 2026
0f983f6
Merge #1176 formatter lineage into Slider branch
seonghobae Sep 9, 2026
ed0e960
Merge #1176 formatter lineage into Slider branch
seonghobae Sep 9, 2026
913d981
test(ui): pin slider hit-target anchoring
seonghobae Sep 10, 2026
a800e76
test(ui): restore unrelated breadcrumb assertion state
seonghobae Sep 10, 2026
401c68b
fix(ui): anchor slider hit target to thumb
seonghobae Sep 10, 2026
3676076
docs(ui): record slider target geometry boundary
seonghobae Sep 10, 2026
d6401bc
fix(ui): anchor slider hit target to thumb
seonghobae Sep 10, 2026
1b789fd
docs(ui): restore slider target geometry traceability
seonghobae Sep 10, 2026
b17d894
docs(ui): bind slider target rationale to Base UI 1.7
seonghobae Sep 10, 2026
1cf1453
docs(ui): restore slider target geometry traceability
seonghobae Sep 10, 2026
ec9e962
docs(ui): preserve primary slider target evidence
seonghobae Sep 10, 2026
6dec187
docs(ui): preserve primary slider target evidence
seonghobae Sep 10, 2026
c2ba832
test: reject redundant slider positioning token
seonghobae Sep 10, 2026
0f53097
fix: rely on Base UI thumb positioning
seonghobae Sep 10, 2026
d5c1820
docs: correct slider target positioning evidence
seonghobae Sep 10, 2026
4b87f1a
docs: correct slider target positioning evidence
seonghobae Sep 10, 2026
65f8908
docs: correct slider target positioning evidence
seonghobae Sep 10, 2026
618afdf
docs: correct slider target positioning evidence
seonghobae Sep 10, 2026
1d27109
docs: correct slider target positioning evidence
seonghobae Sep 10, 2026
7b2ecc6
docs: correct slider target positioning evidence
seonghobae Sep 10, 2026
dc8ab81
docs: correct slider target positioning evidence
seonghobae Sep 10, 2026
cb36971
test(ui): expose slider track clipping contract
seonghobae Sep 10, 2026
1183b6b
fix(ui): keep slider targets inside canonical track anatomy
seonghobae Sep 10, 2026
9238a39
test(ui): preserve slider described-range semantics
seonghobae Sep 10, 2026
34ae130
fix(ui): keep slider targets inside canonical track anatomy
seonghobae Sep 10, 2026
7c19850
test(ui): restore inherited described-range contract
seonghobae Sep 10, 2026
a53696c
docs(ui): add rehearsal range slider story
seonghobae Sep 10, 2026
d52bf25
test(ui): require usable slider track-press target
seonghobae Sep 10, 2026
b1339a0
docs(ui): add rehearsal range slider story
seonghobae Sep 10, 2026
cc78a48
test(ui): restore slider track-press target RED
seonghobae Sep 10, 2026
0098a04
fix(ui): preserve slider track-press target size
seonghobae Sep 10, 2026
14bb414
docs(ui): record canonical slider primitive
seonghobae Sep 10, 2026
d24834b
docs(ui): record canonical slider primitive
seonghobae Sep 10, 2026
a10b223
docs(ui): record canonical slider primitive
seonghobae Sep 10, 2026
fd2385c
docs(ui): record canonical slider primitive
seonghobae Sep 11, 2026
342edca
docs(ui): record canonical slider primitive
seonghobae Sep 11, 2026
a936b9d
docs(ui): record canonical slider primitive
seonghobae Sep 11, 2026
f301bc6
test(ui): pin canonical slider thumb focus and hit-target ownership
seonghobae Sep 16, 2026
834fe9a
fix(ui): bind canonical slider focus and hit target to each thumb
seonghobae Sep 16, 2026
6c3ca6d
test(ui): pin Base UI nested slider focus owner
seonghobae Sep 16, 2026
126671b
fix(ui): restore nested-input slider focus styling
seonghobae Sep 16, 2026
8804142
test(ui): restore Base UI slider geometry contract
seonghobae Sep 16, 2026
480ba90
fix(ui): honor Base UI slider focus and placement owners
seonghobae Sep 16, 2026
9fb3f83
docs(ui): correct Slider focus ownership evidence
seonghobae Sep 16, 2026
28ee3da
docs(changelog): record Slider focus ownership repair
seonghobae Sep 16, 2026
18f8d41
build(ui): adopt base-ui 1.8.0 to resolve slider ref stability
seonghobae Sep 16, 2026
60440b7
repair(ui): preserve Base UI 1.8 focus and placement regressions
seonghobae Sep 16, 2026
625419e
docs(ui): keep Slider focus and geometry evidence current for Base UI…
seonghobae Sep 16, 2026
18492d7
docs(ui): record Base UI 1.8 adoption without dropping Slider safeguards
seonghobae Sep 16, 2026
d2c0164
test(ui): require visible Slider label association
seonghobae Sep 23, 2026
29b7d07
fix(ui): preserve visible Slider label association
seonghobae Sep 23, 2026
ff7d5f1
fix(ui): preserve visible Slider label association
seonghobae Sep 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@

### Added

- Add the reusable Base UI Slider primitive with named scalar/range thumbs, horizontal and vertical composition, RTL keyboard semantics, disabled-state handling, and Storybook coverage for rehearsal range controls.
- Name tonight's first playable range on the ready rehearsal map and tell the player to check that span on their instrument before the section.
- Display the analyzed song tempo (BPM) as a badge in the rehearsal workspace.
- 각 합주 역할(Role)별 개인 연습 진행도를 0~100% 범위로 기록 및 시각화할 수 있는 연습 진척도(`practiceProgress`) 트래커 기능 추가. UI 컨트롤(슬라이더 및 +/- 버튼)과 한/영 다국어 지원 포함.

### Changed

- Upgrade Base UI to 1.8.0 while preserving BandScope's canonical Slider composition, named-thumb accessibility, nested-input focus styling, and dependency-owned absolute thumb placement.
- Consolidated Bandit, dependency audits, supplemental secret checks, and Trivy into one trusted-branch security backstop, delegated CodeQL to GitHub default setup, and removed duplicate local PR security and release-preflight runs.
- Pinned npm `10.9.9` as the approved lockfile generator, activated it through Node-bundled Corepack before dependency consumption, and fail closed unless its bundled `tar` is at least `7.5.19`; primary CI still consumes the committed lock only through frozen `npm ci` validation, rejects mutable npm resolution in the lock gate, requires integrity evidence for public-registry lock entries, and preserves generator-sensitive root `@esbuild/*` peer metadata.

### Fixed

- Keep Slider keyboard focus paint on the Base UI thumb wrapper through the nested range input's `:focus-visible` state, and preserve Base UI's own absolute thumb-placement authority instead of adding a competing `relative` position token.
- Upgraded the local score PDF parser to `pdfjs-dist` 6.2.108, pinned Undici 7.29.0 across the workspace, and constrained PDF loading to copied in-memory bytes with a same-origin bundled worker and npm-generated lock provenance.

## [0.1.3] - 2026-04-29
Expand Down
3 changes: 2 additions & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
},
"dependencies": {
"@bandscope/shared-types": "0.1.0",
"@base-ui/react": "^1.5.0",
"@base-ui/react": "^1.8.0",
"@fontsource-variable/geist": "^5.2.9",
"@tauri-apps/api": "^2.11.0",
"class-variance-authority": "^0.7.1",
Expand All @@ -33,6 +33,7 @@
"@tauri-apps/cli": "^2.11.4",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@testing-library/user-event": "^14.6.7",
"@types/node": "^26.1.1",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
Expand Down
113 changes: 113 additions & 0 deletions apps/desktop/src/components/ui/slider-composition.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { render, screen } from "@testing-library/react"
import { describe, expect, it } from "vitest"

import {
Slider,
SliderControl,
SliderIndicator,
SliderLabel,
SliderThumb,
SliderTrack,
} from "./slider"

describe("Slider canonical composition", () => {
it("associates a visible SliderLabel with the nested range input", () => {
render(
<Slider defaultValue={50}>
<SliderLabel>Practice progress</SliderLabel>
<SliderControl>
<SliderTrack>
<SliderIndicator />
<SliderThumb />
</SliderTrack>
</SliderControl>
</Slider>
)

expect(screen.getByRole("slider", { name: "Practice progress" })).toHaveAccessibleName(
"Practice progress"
)
})

it("keeps range thumbs inside a usable track-press area without clipping their pointer targets", () => {
const { container } = render(
<Slider defaultValue={[25, 75]}>
<span id="loop-range-help">Selected rehearsal loop boundaries</span>
<SliderControl>
<SliderTrack>
<SliderIndicator />
<SliderThumb
index={0}
aria-label="Loop start"
aria-describedby="loop-range-help"
/>
<SliderThumb index={1} aria-label="Loop end" />
</SliderTrack>
</SliderControl>
</Slider>
)

const control = container.querySelector('[data-slot="slider-control"]')
const track = container.querySelector('[data-slot="slider-track"]')
const start = screen.getByRole("slider", { name: "Loop start" })
const end = screen.getByRole("slider", { name: "Loop end" })
const startThumb = start.parentElement
const endThumb = end.parentElement

expect(control).toHaveClass("min-h-6")
expect(track).toBeInTheDocument()
expect(track).toContainElement(start)
expect(track).toContainElement(end)
expect(track).not.toHaveClass("overflow-hidden")
expect(start).toHaveAttribute("aria-describedby", "loop-range-help")
expect(startThumb).toHaveStyle({ position: "absolute" })
expect(endThumb).toHaveStyle({ position: "absolute" })
expect(startThumb).not.toHaveClass("relative")
expect(endThumb).not.toHaveClass("relative")
expect(startThumb).toHaveClass("after:inset-[-12px]")
expect(endThumb).toHaveClass("after:inset-[-12px]")
})

it("styles keyboard focus from Base UI's nested range input", () => {
render(
<Slider defaultValue={50}>
<SliderControl>
<SliderTrack>
<SliderIndicator />
<SliderThumb aria-label="Playback position" />
</SliderTrack>
</SliderControl>
</Slider>
)

const input = screen.getByRole("slider", { name: "Playback position" })
const thumb = input.parentElement

expect(input).toHaveAttribute("type", "range")
expect(thumb).toHaveClass("has-[:focus-visible]:outline-none")
expect(thumb).toHaveClass("has-[:focus-visible]:ring-2")
expect(thumb).not.toHaveClass("focus-visible:ring-2")
})

it("preserves the same track-owned thumb anatomy and press width for a vertical control", () => {
const { container } = render(
<Slider defaultValue={50} orientation="vertical">
<SliderControl>
<SliderTrack>
<SliderIndicator />
<SliderThumb aria-label="Vertical position" />
</SliderTrack>
</SliderControl>
</Slider>
)

const control = container.querySelector('[data-slot="slider-control"]')
const track = container.querySelector('[data-slot="slider-track"]')
const thumb = screen.getByRole("slider", { name: "Vertical position" })

expect(thumb).toHaveAttribute("aria-orientation", "vertical")
expect(control).toHaveClass("data-[orientation=vertical]:min-w-6")
expect(track).toContainElement(thumb)
expect(track).not.toHaveClass("overflow-hidden")
})
})
123 changes: 123 additions & 0 deletions apps/desktop/src/components/ui/slider.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import type { Meta, StoryObj } from "@storybook/react-vite"

import { DirectionProvider } from "@base-ui/react/direction-provider"

import {
Slider,
SliderControl,
SliderTrack,
SliderIndicator,
SliderThumb,
} from "./slider"

/** Base UI Slider primitive documentation. */
const meta = {
title: "UI/Slider",
component: Slider,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
} satisfies Meta<typeof Slider>

export default meta
type Story = StoryObj<typeof meta>

/** Default slider view rendering. */
export const Default: Story = {
render: (args) => (
<div className="w-[200px]">
<Slider defaultValue={50} {...args}>
<SliderControl>
<SliderTrack>
<SliderIndicator />
<SliderThumb aria-label="Volume" />
</SliderTrack>
</SliderControl>
</Slider>
</div>
),
}

/** Two-thumb selected-range rendering for rehearsal loop boundaries. */
export const Range: Story = {
render: (args) => (
<div className="w-[280px]">
<Slider defaultValue={[25, 75]} {...args}>
<SliderControl>
<SliderTrack>
<SliderIndicator />
<SliderThumb index={0} aria-label="Loop start" />
<SliderThumb index={1} aria-label="Loop end" />
</SliderTrack>
</SliderControl>
</Slider>
</div>
),
}

/** Vertical orientation rendering. */
export const Vertical: Story = {
render: (args) => (
<div className="h-[200px]">
<Slider defaultValue={50} orientation="vertical" {...args}>
<SliderControl>
<SliderTrack>
<SliderIndicator />
<SliderThumb aria-label="Volume" />
</SliderTrack>
</SliderControl>
</Slider>
</div>
),
}

/** Disabled interaction rendering. */
export const Disabled: Story = {
render: (args) => (
<div className="w-[200px]">
<Slider defaultValue={50} disabled {...args}>
<SliderControl>
<SliderTrack>
<SliderIndicator />
<SliderThumb aria-label="Volume" />
</SliderTrack>
</SliderControl>
</Slider>
</div>
),
}

/** Stepped value rendering. */
export const CustomStep: Story = {
render: (args) => (
<div className="w-[200px]">
<Slider defaultValue={50} step={10} {...args}>
<SliderControl>
<SliderTrack>
<SliderIndicator />
<SliderThumb aria-label="Volume" />
</SliderTrack>
</SliderControl>
</Slider>
</div>
),
}

/** Right-to-left reading direction rendering. */
export const RTL: Story = {
render: (args) => (
<DirectionProvider direction="rtl">
<div className="w-[200px]" dir="rtl">
<Slider defaultValue={50} {...args}>
<SliderControl>
<SliderTrack>
<SliderIndicator />
<SliderThumb aria-label="Volume" />
</SliderTrack>
</SliderControl>
</Slider>
</div>
</DirectionProvider>
),
}
Loading