Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,22 @@ easy to spot when bumping.

## [Unreleased]

## [8.0.0] - 2026-09-29

### Breaking

- **genre-tree**: playing a genre playlist now pages its tracks from `GET {me/}genre-playlists/{uuid}/tracks/` (100 per page) instead of fetching the playlist detail, so the backend must serve that endpoint. `playNewTrackListFromGenrePlaylist(genrePlaylist, scope)` takes the tree's list item and returns a Promise.
- **genre-tree**: `GenreTreeView` drops the `criteriaPlaylistDetailedSchema` prop and its track generic; tracks are parsed with `TrackListProvider`'s `schema`.
- **genre-tree**: `CriteriaPlaylistDetailedSchema` is metadata-only (no `trackPlaylistRelations`). `makeCriteriaPlaylistDetailedSchema`, `CriteriaPlaylistDetailedLike` and `useFetchGenrePlaylistDetailed` are removed.
- **genre-tree**: `TrackBaseSchema` no longer has `playlists`.
- **genre-tree**: `TrackListSidebar` uses `IntersectionObserver` when the list has more pages; consumer tests rendering it under jsdom must stub it.

### Added

- **genre-tree**: `TrackList` carries `total` and `nextPage`; `useTrackList().loadMore()` appends the next page, and the provider loads it automatically when the selected track is within 10 of the end.
- **genre-tree**: `TrackListSidebar` shows the playlist's total track count and loads more tracks when scrolled to the bottom.
- **genre-tree**: `genrePlaylistEndpoints.tracks(uuid)` and `makeTrackPlaylistRelPageSchema(trackSchema)`.

## [7.0.2] - 2026-09-28

### Fixed
Expand Down
6 changes: 0 additions & 6 deletions apps/playground/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
libraryQueryKeys,
YoutubeTrackDetailed,
YoutubeTrackDetailedSchema,
makeCriteriaPlaylistDetailedSchema,
} from "@behindthemusictree/app-kit";
import { Button, RingLoader, Skeleton } from "@behindthemusictree/ui";
import GenreCreationPopup from "./GenreCreationPopup";
Expand Down Expand Up @@ -47,10 +46,6 @@ function useLoadTrack(): (trackId: string) => Promise<PlayerTrack> {
);
}

const criteriaPlaylistDetailedSchema = makeCriteriaPlaylistDetailedSchema(
YoutubeTrackDetailedSchema,
);

function ReferenceGenreTree() {
const { showPopup, hidePopup } = usePopup();

Expand Down Expand Up @@ -88,7 +83,6 @@ function ReferenceGenreTree() {
handleGenreCreationAction={showCriteriaCreationPopup}
handleGenreRenameAction={showGenreRenamePopup}
getBackendBaseUrl={getBackendBaseUrl}
criteriaPlaylistDetailedSchema={criteriaPlaylistDetailedSchema}
/>
);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/app-kit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@behindthemusictree/app-kit",
"version": "7.0.2",
"version": "8.0.0",
"description": "Shared transport, auth, popup, UI, player, and genre-tree plumbing for BehindTheMusicTree React apps",
"repository": {
"type": "git",
Expand Down
14 changes: 4 additions & 10 deletions packages/app-kit/src/genre-tree/GenreTreeView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,8 @@ vi.mock("@behindthemusictree/genre-tree-view", async (importOriginal) => ({
}));

import { GenreTreeView, type GenreTreeViewProps } from "./GenreTreeView";
import type { TrackBase } from "./schemas/track/base";
import type { CriteriaPlaylistDetailedLike } from "./models/TrackListOrigin";

const getBackendBaseUrl = () => "https://backend.example.com";
const schema = z.custom<CriteriaPlaylistDetailedLike<TrackBase>>();

// requestAnimationFrame isn't driven by fake timers in jsdom — stub it onto a manually-flushable
// queue so tests can step through GenreTreeWheelHandoff's two nested rAFs deterministically.
Expand Down Expand Up @@ -102,13 +99,12 @@ function makePlaylist(overrides: Record<string, unknown> = {}) {
};
}

function renderView(overrides: Partial<GenreTreeViewProps<TrackBase>> = {}) {
const props: GenreTreeViewProps<TrackBase> = {
function renderView(overrides: Partial<GenreTreeViewProps> = {}) {
const props: GenreTreeViewProps = {
scope: "me",
handleGenreCreationAction: vi.fn(),
handleGenreRenameAction: vi.fn(),
getBackendBaseUrl,
criteriaPlaylistDetailedSchema: schema,
...overrides,
};
render(<GenreTreeView {...props} />);
Expand Down Expand Up @@ -487,12 +483,11 @@ describe("GenreTreeView", () => {
data: undefined,
isPending: true,
});
const props: GenreTreeViewProps<TrackBase> = {
const props: GenreTreeViewProps = {
scope: "me",
handleGenreCreationAction: vi.fn(),
handleGenreRenameAction: vi.fn(),
getBackendBaseUrl,
criteriaPlaylistDetailedSchema: schema,
};
const { rerender } = render(<GenreTreeView {...props} />);

Expand Down Expand Up @@ -533,12 +528,11 @@ describe("GenreTreeView", () => {
data: undefined,
isPending: true,
});
const props: GenreTreeViewProps<TrackBase> = {
const props: GenreTreeViewProps = {
scope: "me",
handleGenreCreationAction: vi.fn(),
handleGenreRenameAction: vi.fn(),
getBackendBaseUrl,
criteriaPlaylistDetailedSchema: schema,
};
const { rerender } = render(<GenreTreeView {...props} />);

Expand Down
26 changes: 3 additions & 23 deletions packages/app-kit/src/genre-tree/GenreTreeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ import type {

import { CriteriaPlaylistSimple } from "./schemas/criteria-playlist/simple";
import { CriteriaMinimum } from "./schemas/criteria/minimum";
import { TrackBase } from "./schemas/track/base";
import { CriteriaOverview } from "./schemas/criteria/overview";
import { CriteriaPlaylistDetailedLike } from "./models/TrackListOrigin";
import { Scope } from "../transport/lib/scope";
import { useListFullGenrePlaylists } from "./useGenrePlaylist";
import { usePrefetchGenreOverview } from "./useGenre";
Expand All @@ -39,15 +37,11 @@ export type { GenreTreeViewMode } from "@behindthemusictree/genre-tree-view";

const HOVER_PREFETCH_DELAY_MS = 100;

export type GenreTreeViewProps<
T extends TrackBase,
O extends CriteriaOverview = CriteriaOverview,
> = {
export type GenreTreeViewProps<O extends CriteriaOverview = CriteriaOverview> = {
scope: Scope;
handleGenreCreationAction: (parent: CriteriaMinimum | null) => void;
handleGenreRenameAction: (genre: CriteriaMinimum) => void;
getBackendBaseUrl: () => string;
criteriaPlaylistDetailedSchema: z.ZodType<CriteriaPlaylistDetailedLike<T>>;
additionalActions?: (node: GenreTreeNode) => GenreTreeAction[];
/** Controlled view mode. When provided, the internal Stacked/Wheel toggle is not rendered — the consumer owns that UI. */
viewMode?: GenreTreeViewMode;
Expand All @@ -60,21 +54,17 @@ export type GenreTreeViewProps<
renderGenreDetailExtras?: (overview: O) => ReactNode;
};

export function GenreTreeView<
T extends TrackBase,
O extends CriteriaOverview = CriteriaOverview,
>({
export function GenreTreeView<O extends CriteriaOverview = CriteriaOverview>({
scope,
handleGenreCreationAction,
handleGenreRenameAction,
getBackendBaseUrl,
criteriaPlaylistDetailedSchema,
additionalActions,
viewMode: controlledViewMode,
readOnly = false,
criteriaOverviewSchema,
renderGenreDetailExtras,
}: GenreTreeViewProps<T, O>) {
}: GenreTreeViewProps<O>) {
const [reparentingGenreUuid, setReparentingGenreUuid] = useState<
string | null
>(null);
Expand Down Expand Up @@ -310,9 +300,6 @@ export function GenreTreeView<
handleGenreCreationAction={handleGenreCreationAction}
handleGenreRenameAction={handleGenreRenameAction}
getBackendBaseUrl={getBackendBaseUrl}
criteriaPlaylistDetailedSchema={
criteriaPlaylistDetailedSchema
}
additionalActions={additionalActions}
onNodeClick={handleNodeClick}
onNodeHover={handleNodeHover}
Expand Down Expand Up @@ -340,9 +327,6 @@ export function GenreTreeView<
handleGenreCreationAction={handleGenreCreationAction}
handleGenreRenameAction={handleGenreRenameAction}
getBackendBaseUrl={getBackendBaseUrl}
criteriaPlaylistDetailedSchema={
criteriaPlaylistDetailedSchema
}
additionalActions={additionalActions}
onNodeClick={handleNodeClick}
onNodeHover={handleNodeHover}
Expand All @@ -366,7 +350,6 @@ export function GenreTreeView<
handleGenreCreationAction={handleGenreCreationAction}
handleGenreRenameAction={handleGenreRenameAction}
getBackendBaseUrl={getBackendBaseUrl}
criteriaPlaylistDetailedSchema={criteriaPlaylistDetailedSchema}
additionalActions={additionalActions}
onNodeClick={handleNodeClick}
onNodeHover={handleNodeHover}
Expand Down Expand Up @@ -395,9 +378,6 @@ export function GenreTreeView<
handleGenreCreationAction={handleGenreCreationAction}
handleGenreRenameAction={handleGenreRenameAction}
getBackendBaseUrl={getBackendBaseUrl}
criteriaPlaylistDetailedSchema={
criteriaPlaylistDetailedSchema
}
additionalActions={additionalActions}
onNodeClick={handleNodeClick}
onNodeHover={handleNodeHover}
Expand Down
Loading
Loading