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
6 changes: 3 additions & 3 deletions docs/USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,13 +350,13 @@ The Tracked tab lets you pin issues and PRs into a personal TODO list that you c

## Repo Pinning

Each repo group header has a pin (lock) control, visible on hover on desktop and always visible on mobile. Pinning a repo keeps it at the top of the list within its tab regardless of sort order or how recently it was updated.
Each repo group header has a pin (lock) control, visible on hover on desktop and always visible on mobile. Pinning a repo keeps it at the top of the list on all tabs regardless of sort order or how recently it was updated.

- Click the pin icon to pin a repo to the top.
- Click it again to unpin.
- Use the up/down arrows (visible when pinned) to reorder pinned repos relative to each other.

Pin state is per-tab — a repo can be pinned on the Issues tab but not the Pull Requests tab.
Pin state is shared across all tabs — pinning a repo on the Issues tab also pins it on Pull Requests and Actions.

---

Expand Down Expand Up @@ -438,7 +438,7 @@ These are UI preferences that persist across sessions but are not included in th
| Show PR runs (Actions) | Off | Whether to show workflow runs triggered by pull request events. |
| Hide Dependency Dashboard | On | Whether to hide the Renovate Dependency Dashboard issue. |
| Sort preferences | Updated (desc) | Sort field and direction per tab, remembered across sessions. |
| Pinned repos | (none) | Repos pinned to the top of each tab's list. |
| Pinned repos | (none) | Repos pinned to the top of the list across all tabs. |
| Tracked items | (none) | Issues and PRs pinned to the Tracked tab (max 200). |

---
Expand Down
2 changes: 1 addition & 1 deletion scripts/waf-smoke-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ TESTS=(
# --- Run in parallel (::: passes array elements directly, avoiding stdin quoting issues) ---
TOTAL=${#TESTS[@]}

OUTPUT=$(parallel --will-cite -k -j10 --timeout 15 run_spec ::: "${TESTS[@]}") || true
OUTPUT=$(parallel --will-cite -k -j2 --delay 0.3 --timeout 15 run_spec ::: "${TESTS[@]}") || true

# Detect infrastructure failure (parallel crashed, no tests ran)
if [[ -z "$OUTPUT" ]]; then
Expand Down
11 changes: 6 additions & 5 deletions src/app/components/dashboard/ActionsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface ActionsTabProps {
workflowRuns: WorkflowRun[];
loading?: boolean;
hasUpstreamRepos?: boolean;
configRepoNames?: string[];
refreshTick?: number;
hotPollingRunIds?: ReadonlySet<number>;
}
Expand Down Expand Up @@ -131,7 +132,7 @@ export default function ActionsTab(props: ActionsTabProps) {
}

const activeRepoNames = createMemo(() =>
[...new Set(props.workflowRuns.map((r) => r.repoFullName))]
props.configRepoNames ?? [...new Set(props.workflowRuns.map((r) => r.repoFullName))]
);

const ignoredWorkflowRuns = createMemo(() =>
Expand Down Expand Up @@ -201,18 +202,18 @@ export default function ActionsTab(props: ActionsTabProps) {
});

const repoGroups = createMemo(() =>
orderRepoGroups(groupRuns(filteredRuns()), viewState.lockedRepos.actions)
orderRepoGroups(groupRuns(filteredRuns()), viewState.lockedRepos)
);

createEffect(() => {
const names = activeRepoNames();
if (names.length === 0) return;
pruneLockedRepos("actions", names);
pruneLockedRepos(names);
});

const highlightedReposActions = createReorderHighlight(
() => repoGroups().map(g => g.repoFullName),
() => viewState.lockedRepos.actions,
() => viewState.lockedRepos,
() => ignoredWorkflowRuns().length,
() => JSON.stringify(viewState.tabFilters.actions),
);
Expand Down Expand Up @@ -327,7 +328,7 @@ export default function ActionsTab(props: ActionsTabProps) {
</Show>
</button>
<RepoGitHubLink repoFullName={repoGroup.repoFullName} section="actions" />
<RepoLockControls tab="actions" repoFullName={repoGroup.repoFullName} />
<RepoLockControls repoFullName={repoGroup.repoFullName} />
</div>
<Show when={!isExpanded() && peekUpdates().get(repoGroup.repoFullName)}>
{(peek) => (
Expand Down
7 changes: 7 additions & 0 deletions src/app/components/dashboard/DashboardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,10 @@ export default function DashboardPage() {
];
});

const configRepoNames = createMemo(() =>
[...new Set([...config.selectedRepos, ...config.upstreamRepos, ...config.monitoredRepos].map(r => r.fullName))]
);

return (
<div class="min-h-screen bg-base-200">
<Header />
Expand Down Expand Up @@ -550,6 +554,7 @@ export default function DashboardPage() {
allUsers={allUsers()}
trackedUsers={config.trackedUsers}
monitoredRepos={config.monitoredRepos}
configRepoNames={configRepoNames()}
refreshTick={refreshTick()}
/>
</Match>
Expand All @@ -562,6 +567,7 @@ export default function DashboardPage() {
trackedUsers={config.trackedUsers}
hotPollingPRIds={hotPollingPRIds()}
monitoredRepos={config.monitoredRepos}
configRepoNames={configRepoNames()}
refreshTick={refreshTick()}
/>
</Match>
Expand All @@ -579,6 +585,7 @@ export default function DashboardPage() {
workflowRuns={dashboardData.workflowRuns}
loading={dashboardData.loading}
hasUpstreamRepos={config.upstreamRepos.length > 0}
configRepoNames={configRepoNames()}
refreshTick={refreshTick()}
hotPollingRunIds={hotPollingRunIds()}
/>
Expand Down
11 changes: 6 additions & 5 deletions src/app/components/dashboard/IssuesTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export interface IssuesTabProps {
allUsers?: { login: string; label: string }[];
trackedUsers?: TrackedUser[];
monitoredRepos?: RepoRef[];
configRepoNames?: string[];
refreshTick?: number;
}

Expand Down Expand Up @@ -191,7 +192,7 @@ export default function IssuesTab(props: IssuesTabProps) {
const issueMeta = createMemo(() => filteredSortedWithMeta().meta);

const repoGroups = createMemo(() =>
orderRepoGroups(groupByRepo(filteredSorted()), viewState.lockedRepos.issues)
orderRepoGroups(groupByRepo(filteredSorted()), viewState.lockedRepos)
);
const pageLayout = createMemo(() => computePageLayout(repoGroups(), config.itemsPerPage));
const pageCount = createMemo(() => pageLayout().pageCount);
Expand All @@ -205,7 +206,7 @@ export default function IssuesTab(props: IssuesTabProps) {
});

const activeRepoNames = createMemo(() =>
[...new Set(props.issues.map((i) => i.repoFullName))]
props.configRepoNames ?? [...new Set(props.issues.map((i) => i.repoFullName))]
);

createEffect(() => {
Expand All @@ -217,7 +218,7 @@ export default function IssuesTab(props: IssuesTabProps) {
createEffect(() => {
const names = activeRepoNames();
if (names.length === 0) return;
pruneLockedRepos("issues", names);
pruneLockedRepos(names);
});

const trackedIssueIds = createMemo(() =>
Expand All @@ -228,7 +229,7 @@ export default function IssuesTab(props: IssuesTabProps) {

const highlightedReposIssues = createReorderHighlight(
() => repoGroups().map(g => g.repoFullName),
() => viewState.lockedRepos.issues,
() => viewState.lockedRepos,
() => ignoredIssues().length,
() => JSON.stringify(viewState.tabFilters.issues),
);
Expand Down Expand Up @@ -386,7 +387,7 @@ export default function IssuesTab(props: IssuesTabProps) {
</Show>
</button>
<RepoGitHubLink repoFullName={repoGroup.repoFullName} section="issues" />
<RepoLockControls tab="issues" repoFullName={repoGroup.repoFullName} />
<RepoLockControls repoFullName={repoGroup.repoFullName} />
</div>
<Show when={isExpanded()}>
<div role="list" class="divide-y divide-base-300">
Expand Down
11 changes: 6 additions & 5 deletions src/app/components/dashboard/PullRequestsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface PullRequestsTabProps {
trackedUsers?: TrackedUser[];
hotPollingPRIds?: ReadonlySet<number>;
monitoredRepos?: RepoRef[];
configRepoNames?: string[];
refreshTick?: number;
}

Expand Down Expand Up @@ -288,7 +289,7 @@ export default function PullRequestsTab(props: PullRequestsTabProps) {
const prMeta = createMemo(() => filteredSortedWithMeta().meta);

const repoGroups = createMemo(() =>
orderRepoGroups(groupByRepo(filteredSorted()), viewState.lockedRepos.pullRequests)
orderRepoGroups(groupByRepo(filteredSorted()), viewState.lockedRepos)
);
const pageLayout = createMemo(() => computePageLayout(repoGroups(), config.itemsPerPage));
const pageCount = createMemo(() => pageLayout().pageCount);
Expand All @@ -302,7 +303,7 @@ export default function PullRequestsTab(props: PullRequestsTabProps) {
});

const activeRepoNames = createMemo(() =>
[...new Set(props.pullRequests.map((pr) => pr.repoFullName))]
props.configRepoNames ?? [...new Set(props.pullRequests.map((pr) => pr.repoFullName))]
);

createEffect(() => {
Expand All @@ -314,7 +315,7 @@ export default function PullRequestsTab(props: PullRequestsTabProps) {
createEffect(() => {
const names = activeRepoNames();
if (names.length === 0) return;
pruneLockedRepos("pullRequests", names);
pruneLockedRepos(names);
});

const { flashingIds: flashingPRIds, peekUpdates } = createFlashDetection({
Expand All @@ -334,7 +335,7 @@ export default function PullRequestsTab(props: PullRequestsTabProps) {

const highlightedReposPRs = createReorderHighlight(
() => repoGroups().map(g => g.repoFullName),
() => viewState.lockedRepos.pullRequests,
() => viewState.lockedRepos,
() => ignoredPullRequests().length,
() => JSON.stringify(viewState.tabFilters.pullRequests),
);
Expand Down Expand Up @@ -535,7 +536,7 @@ export default function PullRequestsTab(props: PullRequestsTabProps) {
</Show>
</button>
<RepoGitHubLink repoFullName={repoGroup.repoFullName} section="pulls" />
<RepoLockControls tab="pullRequests" repoFullName={repoGroup.repoFullName} />
<RepoLockControls repoFullName={repoGroup.repoFullName} />
</div>
<Show when={!isExpanded() && peekUpdates().get(repoGroup.repoFullName)}>
{(peek) => (
Expand Down
13 changes: 6 additions & 7 deletions src/app/components/shared/RepoLockControls.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { Show, createMemo } from "solid-js";
import { viewState, lockRepo, unlockRepo, moveLockedRepo, type LockedReposTab } from "../../stores/view";
import { viewState, lockRepo, unlockRepo, moveLockedRepo } from "../../stores/view";
import { Tooltip } from "./Tooltip";
import { withFlipAnimation } from "../../lib/scroll";

interface RepoLockControlsProps {
tab: LockedReposTab;
repoFullName: string;
}

export default function RepoLockControls(props: RepoLockControlsProps) {
const lockInfo = createMemo(() => {
const list = viewState.lockedRepos[props.tab];
const list = viewState.lockedRepos;
const idx = list.indexOf(props.repoFullName);
return {
isLocked: idx !== -1,
Expand All @@ -27,7 +26,7 @@ export default function RepoLockControls(props: RepoLockControlsProps) {
<Tooltip content="Pin to top">
<button
class="btn btn-ghost btn-xs opacity-0 group-hover/repo-header:opacity-100 focus:opacity-100 max-sm:opacity-60 sm:max-lg:opacity-60 transition-opacity"
onClick={() => withFlipAnimation(() => lockRepo(props.tab, props.repoFullName))}
onClick={() => withFlipAnimation(() => lockRepo(props.repoFullName))}
aria-label={`Pin ${props.repoFullName} to top of list`}
>
{/* Heroicons 20px solid: lock-open */}
Expand All @@ -41,7 +40,7 @@ export default function RepoLockControls(props: RepoLockControlsProps) {
<Tooltip content="Unpin">
<button
class="btn btn-ghost btn-xs"
onClick={() => withFlipAnimation(() => unlockRepo(props.tab, props.repoFullName))}
onClick={() => withFlipAnimation(() => unlockRepo(props.repoFullName))}
aria-label={`Unpin ${props.repoFullName}`}
>
{/* Heroicons 20px solid: lock-closed */}
Expand All @@ -53,7 +52,7 @@ export default function RepoLockControls(props: RepoLockControlsProps) {
<Tooltip content={lockInfo().isFirst ? "Already at top of pinned list" : "Move up"}>
<button
class="btn btn-ghost btn-xs"
onClick={() => withFlipAnimation(() => moveLockedRepo(props.tab, props.repoFullName, "up"))}
onClick={() => withFlipAnimation(() => moveLockedRepo(props.repoFullName, "up"))}
disabled={lockInfo().isFirst}
aria-label={`Move ${props.repoFullName} up`}
>
Expand All @@ -66,7 +65,7 @@ export default function RepoLockControls(props: RepoLockControlsProps) {
<Tooltip content={lockInfo().isLast ? "Already at bottom of pinned list" : "Move down"}>
<button
class="btn btn-ghost btn-xs"
onClick={() => withFlipAnimation(() => moveLockedRepo(props.tab, props.repoFullName, "down"))}
onClick={() => withFlipAnimation(() => moveLockedRepo(props.repoFullName, "down"))}
disabled={lockInfo().isLast}
aria-label={`Move ${props.repoFullName} down`}
>
Expand Down
Loading