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
10 changes: 0 additions & 10 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ jobs:
cache-to: type=gha,mode=max

- name: Update release readiness record
id: readiness
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NIGHTLY_VERSION: ${{ steps.version.outputs.value }}
Expand All @@ -80,12 +79,3 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NIGHTLY_VERSION: ${{ steps.version.outputs.value }}
run: bash scripts/notify-github-status.sh nightly

- name: Notify Discord announcements channel
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DISCORD_WEBHOOK_ANNOUNCEMENTS: ${{ secrets.DISCORD_WEBHOOK_ANNOUNCEMENTS }}
READINESS_ISSUE: ${{ steps.readiness.outputs.readiness_issue }}
PULL_LIST: ${{ steps.readiness.outputs.pull_list }}
ISSUE_LIST: ${{ steps.readiness.outputs.issue_list }}
run: bash scripts/notify-discord.sh nightly
19 changes: 19 additions & 0 deletions .tests/frontend/audio-queue.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import assert from "node:assert/strict";
import test from "node:test";

import { normalizeFlowTrack } from "../../frontend/src/utils/audioQueue.js";

const track = {
id: "flow-track",
trackName: "Track",
artistName: "Artist",
streamUrl: "/stream/flow-track",
};

test("flow playback can opt out of listening history", () => {
assert.equal(normalizeFlowTrack(track).recordHistory, true);
assert.equal(
normalizeFlowTrack(track, { recordHistory: false }).recordHistory,
false,
);
});
37 changes: 36 additions & 1 deletion .tests/weekly-flow/playlist-config.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ import {
resetDatabase,
} from "../helpers/backendTestHarness.js";

const [isolatedState, { db }, { dbOps }, playlistConfigModule] =
const [isolatedState, { db }, { dbOps }, playlistConfigModule, flowHandlerUtils] =
await setupIsolatedBackend(
"playlist-config",
"backend/config/db-sqlite.js",
"backend/db/helpers/index.js",
"backend/services/weeklyFlow/weeklyFlowPlaylistConfig.js",
"backend/routes/weeklyFlow/handlers/utils.js",
);
const { flowPlaylistConfig, tracksShareMembership } = playlistConfigModule;
const { validateFlowPayload } = flowHandlerUtils;

test.beforeEach(() => {
resetDatabase(db);
Expand Down Expand Up @@ -56,6 +58,39 @@ test("creates flows with normalized scheduling and enforces unique names", () =>
);
});

test("defaults listening history on and persists a flow opt-out", () => {
const flow = flowPlaylistConfig.createFlow({
name: "No History",
size: 20,
});

assert.equal(flow.recordHistory, true);

const updated = flowPlaylistConfig.updateFlow(flow.id, {
recordHistory: false,
});

assert.equal(updated?.recordHistory, false);
assert.equal(flowPlaylistConfig.getFlow(flow.id)?.recordHistory, false);
});

test("rejects non-boolean listening history payloads", () => {
dbOps.updateSettings({ integrations: { lastfm: { apiKey: "test" } } });
const payload = {
name: "Validated History",
size: 20,
mix: { discover: 100 },
scheduleDays: [1],
};

assert.equal(
validateFlowPayload({ ...payload, recordHistory: "false" }),
"recordHistory must be a boolean",
);
assert.equal(validateFlowPayload({ ...payload, recordHistory: false }), null);
assert.equal(validateFlowPayload(payload), null);
});

test("stores and swaps optional release year range", () => {
const flow = flowPlaylistConfig.createFlow({
name: "Eighties",
Expand Down
4 changes: 4 additions & 0 deletions backend/routes/weeklyFlow/handlers/flows.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export function registerFlows(router) {
mix,
size,
deepDive,
recordHistory,
yearFrom,
yearTo,
tags,
Expand All @@ -97,6 +98,7 @@ export function registerFlows(router) {
mix,
size,
deepDive,
recordHistory,
yearFrom,
yearTo,
tags,
Expand Down Expand Up @@ -133,6 +135,7 @@ export function registerFlows(router) {
mix,
size,
deepDive,
recordHistory,
tags,
relatedArtists,
scheduleDays,
Expand All @@ -150,6 +153,7 @@ export function registerFlows(router) {
mix,
size,
deepDive,
recordHistory,
tags,
relatedArtists,
scheduleDays,
Expand Down
4 changes: 4 additions & 0 deletions backend/routes/weeklyFlow/handlers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export const validateFlowPayload = ({
tags,
relatedArtists,
scheduleDays,
recordHistory,
yearFrom,
yearTo,
} = {}) => {
Expand All @@ -106,6 +107,9 @@ export const validateFlowPayload = ({
if (!Array.isArray(scheduleDays) || scheduleDays.length === 0) {
return "scheduleDays must include at least one day";
}
if (recordHistory !== undefined && typeof recordHistory !== "boolean") {
return "recordHistory must be a boolean";
}
const hasYearFrom = yearFrom != null && String(yearFrom).trim() !== "";
const hasYearTo = yearTo != null && String(yearTo).trim() !== "";
if (hasYearFrom) {
Expand Down
7 changes: 7 additions & 0 deletions backend/services/weeklyFlow/weeklyFlowPlaylistConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ const normalizeFlow = (flow) => {
? Math.trunc(Number(flow.ownerUserId))
: null,
enabled: flow?.enabled === true,
recordHistory: flow?.recordHistory !== false,
scheduleDays: normalizeScheduleDays(flow?.scheduleDays),
scheduleTime: normalizeScheduleTime(flow?.scheduleTime),
deepDive: flow?.deepDive === true,
Expand Down Expand Up @@ -693,6 +694,7 @@ export const flowPlaylistConfig = {
mix,
size,
deepDive,
recordHistory,
yearFrom,
yearTo,
tags,
Expand Down Expand Up @@ -725,6 +727,7 @@ export const flowPlaylistConfig = {
type,
tag,
description,
recordHistory,
scheduleDays,
scheduleTime,
ownerUserId,
Expand Down Expand Up @@ -764,6 +767,10 @@ export const flowPlaylistConfig = {
scheduleDays: updates?.scheduleDays ?? current.scheduleDays,
scheduleTime: updates?.scheduleTime ?? current.scheduleTime,
deepDive: typeof updates?.deepDive === "boolean" ? updates.deepDive : current.deepDive,
recordHistory:
typeof updates?.recordHistory === "boolean"
? updates.recordHistory
: current.recordHistory,
yearFrom,
yearTo,
enabled: current.enabled,
Expand Down
4 changes: 4 additions & 0 deletions docs/src/content/docs/using/flows.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ A flow has three layers:
- **Update hour** chooses the hour for those runs.
- A flow starts only when you enable it. A disabled flow keeps its settings but does not run.

## Listening history

**Record listening history** controls playback from the flow in Aurral's built-in player. Turn it off to keep the flow out of future recommendations and connected scrobblers. Aurral keeps plays recorded before you changed the setting.

## Source mix

Source mix controls where tracks come from.
Expand Down
86 changes: 73 additions & 13 deletions frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -1181,6 +1181,28 @@ textarea {
user-select: none;
}

.sidebar-stage-backdrop::before,
.sidebar-stage-backdrop::after {
pointer-events: none;
position: absolute;
z-index: 1;
content: "";
}

.sidebar-stage-backdrop::before {
inset-block: 0;
inset-inline-end: 0;
width: 6%;
background: linear-gradient(to right, transparent, var(--aurral-chrome));
}

.sidebar-stage-backdrop::after {
inset-inline: 0;
inset-block-end: 0;
height: 16%;
background: linear-gradient(to bottom, transparent, var(--aurral-chrome));
}

.sidebar-stage-backdrop__art {
display: block;
width: 100%;
Expand Down Expand Up @@ -10426,71 +10448,71 @@ button.native-library-genre-row:focus-visible {
border-radius: var(--aurral-radius);
}

.flow-page--playlists .flow-page__library-list {
.flow-page--library .flow-page__library-list {
padding: 0.125rem;
background: transparent;
}

.flow-page--playlists .flow-page__detail {
.flow-page--library .flow-page__detail {
gap: 1.25rem;
padding: 0.5rem 1.25rem 1.5rem;
background: transparent;
border-radius: 0;
}

.flow-page--playlists .flow-page__detail-hero {
.flow-page--library .flow-page__detail-hero {
margin: 0;
padding: 0.75rem 0 1rem;
background: transparent;
border-bottom: 1px solid var(--aurral-border);
border-radius: 0;
}

.flow-page--playlists .flow-page__detail-artwork.flow-page__artwork {
.flow-page--library .flow-page__detail-artwork.flow-page__artwork {
width: 5rem;
height: 5rem;
}

.flow-page--playlists .flow-page__detail-title--hero {
.flow-page--library .flow-page__detail-title--hero {
font-size: clamp(1.75rem, 4vw, 2.75rem);
letter-spacing: -0.04em;
}

.flow-page--playlists .flow-page__tracks {
.flow-page--library .flow-page__tracks {
background: transparent;
border: 0;
border-radius: 0;
}

.flow-page--playlists .flow-page__tracks-toolbar {
.flow-page--library .flow-page__tracks-toolbar {
padding: 0 0 0.625rem;
}

.flow-page--playlists .flow-page__tracks-toolbar .btn-accent {
.flow-page--library .flow-page__tracks-toolbar .btn-accent {
background: var(--aurral-text);
color: var(--aurral-surface);
}

.flow-page--playlists .flow-page__tracks-table-head th {
.flow-page--library .flow-page__tracks-table-head th {
padding: 0.45rem 0.75rem;
}

.flow-page--playlists .flow-page__tracks-table-row td {
.flow-page--library .flow-page__tracks-table-row td {
padding: 0.4rem 0.75rem;
}

.flow-page--playlists .flow-page__tracks-table-artwork {
.flow-page--library .flow-page__tracks-table-artwork {
width: 3rem;
padding: 0.35rem 0.5rem;
}

.flow-page--playlists .flow-page__tracks-table-artwork .flow-page__tracks-table-artwork-thumb {
.flow-page--library .flow-page__tracks-table-artwork .flow-page__tracks-table-artwork-thumb {
width: 2.35rem;
height: 2.35rem;
border-radius: 0.2rem;
}

.flow-page--playlists .flow-page__tracks-table-duration {
.flow-page--library .flow-page__tracks-table-duration {
width: 4.5rem;
color: var(--aurral-text-subtle);
font-variant-numeric: tabular-nums;
Expand Down Expand Up @@ -10958,6 +10980,44 @@ button.native-library-genre-row:focus-visible {
min-width: 0;
}

.flow-page__history-setting {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding-top: 1rem;
border-top: 1px solid var(--aurral-border);
}

.flow-page__history-copy {
display: grid;
min-width: 0;
gap: 0.25rem;
}

.flow-page__history-description {
max-width: 38rem;
margin: 0;
color: var(--aurral-text-muted);
font-size: 0.8125rem;
line-height: 1.45;
}

.flow-page__history-control {
display: inline-flex;
flex-shrink: 0;
align-items: center;
gap: 0.5rem;
}

.flow-page__history-state {
min-width: 1.75rem;
color: var(--aurral-text-muted);
font-size: 0.75rem;
font-weight: 600;
text-align: right;
}

.flow-page__field {
display: grid;
gap: 0.5rem;
Expand Down
Loading
Loading