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
18 changes: 15 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,22 @@ jobs:
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
- uses: astral-sh/setup-uv@v7
- run: corepack enable && corepack install
- run: pnpm install --frozen-lockfile
- run: pnpm run check
- run: pnpm check

native-quality:
runs-on: macos-15
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 24
- run: corepack enable && corepack install
- run: command -v xcodegen >/dev/null || brew install xcodegen
- run: pnpm quality:native
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
worker-configuration.d.ts
worker/agent-edge.mjs
worker/agent-edge.d.mts
29 changes: 20 additions & 9 deletions app/changelog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,23 @@ const releases = [

export const metadata: Metadata = {
title: "Changelog — Setline",
description: "Meaningful improvements to Setline workout execution and history.",
description:
"Meaningful improvements to Setline workout execution and history.",
};

export default function ChangelogPage() {
return (
<main className="changelog-shell">
<header className="changelog-header">
<Link className="wordmark changelog-wordmark" href="/">SETLINE</Link>
<Link className="wordmark changelog-wordmark" href="/">
SETLINE
</Link>
<span className="section-code">PRODUCT HISTORY</span>
<h1>Changelog</h1>
<p>Meaningful improvements to programme execution, rest timing, and workout history.</p>
<p>
Meaningful improvements to programme execution, rest timing, and
workout history.
</p>
<nav aria-label="Project links">
<a href={`${repository}/issues`}>Roadmap</a>
<a href={repository}>Source</a>
Expand All @@ -85,15 +91,20 @@ export default function ChangelogPage() {
<li key={`${release.date}-${release.title}`}>
<article className="changelog-entry">
<time dateTime={release.date}>
{new Date(`${release.date}T00:00:00`).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})}
{new Date(`${release.date}T00:00:00`).toLocaleDateString(
"en-US",
{
year: "numeric",
month: "long",
day: "numeric",
},
)}
</time>
<h2>{release.title}</h2>
<ul>
{release.outcomes.map((outcome) => <li key={outcome}>{outcome}</li>)}
{release.outcomes.map((outcome) => (
<li key={outcome}>{outcome}</li>
))}
</ul>
</article>
</li>
Expand Down
72 changes: 46 additions & 26 deletions app/components/CustomProgrammePlanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ export function CustomProgrammePlanner({
const beginCreate = () => {
if (authoringLocked) return;
if (!customWorkouts.length) {
setError("Create at least one custom workout before writing a programme.");
setError(
"Create at least one custom workout before writing a programme.",
);
return;
}
setError("");
Expand All @@ -119,7 +121,9 @@ export function CustomProgrammePlanner({
if (!programme || authoringLocked) return;
if (
draft &&
!window.confirm("Discard the current unsaved programme draft and reopen the saved copy?")
!window.confirm(
"Discard the current unsaved programme draft and reopen the saved copy?",
)
) {
return;
}
Expand Down Expand Up @@ -247,7 +251,9 @@ export function CustomProgrammePlanner({
onSave(candidate);
setDraft(null);
setError("");
setReceipt(`${candidate.name} saved with ${candidate.assignments.length} assignments.`);
setReceipt(
`${candidate.name} saved with ${candidate.assignments.length} assignments.`,
);
};

const deleteProgramme = () => {
Expand All @@ -262,7 +268,9 @@ export function CustomProgrammePlanner({
}
onDelete();
setDraft(null);
setReceipt(`${programme.name} deleted. Custom workouts and records were kept.`);
setReceipt(
`${programme.name} deleted. Custom workouts and records were kept.`,
);
requestAnimationFrame(() => createButtonRef.current?.focus());
};

Expand All @@ -274,7 +282,9 @@ export function CustomProgrammePlanner({
<div className="custom-programme-heading">
<div>
<span className="section-code">YOUR PROGRAMME</span>
<h2 id="custom-programme-heading">Place each workout on the calendar.</h2>
<h2 id="custom-programme-heading">
Place each workout on the calendar.
</h2>
<p>
One explicit Monday-based block. Empty days stay unplanned; Setline
never generates or progresses the work for you.
Expand Down Expand Up @@ -304,7 +314,8 @@ export function CustomProgrammePlanner({
) : null}
{authoringLocked ? (
<p className="custom-programme-lock" role="status">
Finish or discard the custom workout draft before editing the programme.
Finish or discard the custom workout draft before editing the
programme.
</p>
) : null}

Expand All @@ -317,7 +328,11 @@ export function CustomProgrammePlanner({
{programme && !draft ? (
<article className="custom-programme-summary">
<div>
<span className={programme.enabled ? "status-chip" : "status-chip paused"}>
<span
className={
programme.enabled ? "status-chip" : "status-chip paused"
}
>
{programme.enabled ? "Enabled" : "Paused"}
</span>
<h3>{programme.name}</h3>
Expand Down Expand Up @@ -377,7 +392,9 @@ export function CustomProgrammePlanner({
placeholder="e.g. Four-week strength block"
onChange={(event) =>
setDraft((current) =>
current ? { ...current, name: event.target.value } : current,
current
? { ...current, name: event.target.value }
: current,
)
}
/>
Expand Down Expand Up @@ -412,21 +429,19 @@ export function CustomProgrammePlanner({
{mondayIsoForIsoDate(draft.startsOn) ? (
<button
type="button"
onClick={() =>
{
setDraft((current) =>
current
? {
...current,
startsOn: mondayIsoForIsoDate(current.startsOn),
}
: current,
);
requestAnimationFrame(() =>
startDateInputRef.current?.focus(),
);
}
}
onClick={() => {
setDraft((current) =>
current
? {
...current,
startsOn: mondayIsoForIsoDate(current.startsOn),
}
: current,
);
requestAnimationFrame(() =>
startDateInputRef.current?.focus(),
);
}}
>
Use {mondayIsoForIsoDate(draft.startsOn)}
</button>
Expand All @@ -443,7 +458,9 @@ export function CustomProgrammePlanner({
max={MAX_CUSTOM_PROGRAMME_WEEKS}
inputMode="numeric"
value={draft.weekCount}
onChange={(event) => updateWeekCount(Number(event.target.value))}
onChange={(event) =>
updateWeekCount(Number(event.target.value))
}
/>
</label>
<label className="custom-programme-toggle">
Expand Down Expand Up @@ -475,7 +492,9 @@ export function CustomProgrammePlanner({
<span>Editing week</span>
<select
value={activeWeek}
onChange={(event) => setActiveWeek(Number(event.target.value))}
onChange={(event) =>
setActiveWeek(Number(event.target.value))
}
>
{Array.from(
{ length: draft.weekCount },
Expand All @@ -486,7 +505,8 @@ export function CustomProgrammePlanner({
).length;
return (
<option key={weekNumber} value={weekNumber}>
Week {weekNumber} · {assigned} assigned · {7 - assigned} open
Week {weekNumber} · {assigned} assigned · {7 - assigned}{" "}
open
</option>
);
})}
Expand Down
40 changes: 28 additions & 12 deletions app/components/CustomWorkoutManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ function numberOrNull(value: string) {
return Number.isFinite(parsed) ? parsed : null;
}

function stepForTracking(step: PlannedStep, tracking: TrackingKind): PlannedStep {
function stepForTracking(
step: PlannedStep,
tracking: TrackingKind,
): PlannedStep {
return {
...step,
tracking,
Expand All @@ -71,15 +74,15 @@ function stepForTracking(step: PlannedStep, tracking: TrackingKind): PlannedStep
: null,
targetReps:
tracking === "weight-reps" || tracking === "reps"
? step.targetReps ?? 8
? (step.targetReps ?? 8)
: null,
targetRepsMax:
tracking === "weight-reps" || tracking === "reps"
? step.targetRepsMax
: null,
targetDurationSeconds:
tracking === "duration" || tracking === "weight-duration"
? step.targetDurationSeconds ?? 60
? (step.targetDurationSeconds ?? 60)
: null,
};
}
Expand Down Expand Up @@ -139,7 +142,9 @@ export function CustomWorkoutManager({

const canReplaceDraft = () =>
draft === null ||
window.confirm("Discard the current unsaved draft and open another workout?");
window.confirm(
"Discard the current unsaved draft and open another workout?",
);

const cancelEditing = () => {
if (!window.confirm("Discard this unsaved custom workout draft?")) return;
Expand All @@ -158,7 +163,10 @@ export function CustomWorkoutManager({
setDraft(blankCustomWorkoutTemplate(customWorkoutId()));
};

const beginDuplicate = (source: WorkoutTemplate, opener: HTMLButtonElement) => {
const beginDuplicate = (
source: WorkoutTemplate,
opener: HTMLButtonElement,
) => {
if (!canReplaceDraft()) return;
if (customWorkouts.length >= MAX_CUSTOM_WORKOUTS) {
setError(`Setline keeps at most ${MAX_CUSTOM_WORKOUTS} custom workouts.`);
Expand Down Expand Up @@ -304,7 +312,10 @@ export function CustomWorkoutManager({
};

return (
<section className="custom-workouts" aria-labelledby="custom-workouts-heading">
<section
className="custom-workouts"
aria-labelledby="custom-workouts-heading"
>
<div className="custom-workouts-heading">
<div>
<span className="section-code">YOUR WORKOUTS</span>
Expand Down Expand Up @@ -336,7 +347,8 @@ export function CustomWorkoutManager({
) : null}
{authoringLocked ? (
<p className="custom-workout-lock" role="status">
Finish or discard the programme draft before changing workout templates.
Finish or discard the programme draft before changing workout
templates.
</p>
) : null}

Expand All @@ -351,7 +363,11 @@ export function CustomWorkoutManager({
<div className="custom-editor-heading">
<div>
<span className="section-code">AUTHORING MODE</span>
<h3>{draft.createdAt === draft.updatedAt ? "Custom workout" : "Edit workout"}</h3>
<h3>
{draft.createdAt === draft.updatedAt
? "Custom workout"
: "Edit workout"}
</h3>
</div>
<button type="button" onClick={cancelEditing}>
Cancel
Expand All @@ -368,7 +384,9 @@ export function CustomWorkoutManager({
value={draft.name}
onChange={(event) =>
setDraft((current) =>
current ? { ...current, name: event.target.value } : current,
current
? { ...current, name: event.target.value }
: current,
)
}
placeholder="e.g. Travel-day strength"
Expand Down Expand Up @@ -672,9 +690,7 @@ export function CustomWorkoutManager({
}
>
<span>{template.name}</span>
<small>
{template.steps.length} steps · copy to edit
</small>
<small>{template.steps.length} steps · copy to edit</small>
</button>
))}
</div>
Expand Down
Loading
Loading