diff --git a/API_CONTRACT.md b/API_CONTRACT.md
index ad72e72..8a3dc44 100644
--- a/API_CONTRACT.md
+++ b/API_CONTRACT.md
@@ -140,23 +140,6 @@ always returns normalized refs. Pinning ("Pin Version") holds a container at
its current version: it's never flagged for updates and is grouped into a
separate section, but can still be updated by hand.
-### `GET /api/hidden`
-
-- Auth: cookie.
-- Response: `200` — array of hidden container names.
-
-### `POST /api/hide`
-
-- Auth: cookie.
-- Body: `{ "name": "string" }` — container name to hide from the dashboard.
-- Response: `200 { "ok": true }`. Idempotent.
-
-### `DELETE /api/hide/:name`
-
-- Auth: cookie.
-- Path param: `name` — container name to unhide (URL-encoded).
-- Response: `200 { "ok": true }`. Idempotent.
-
### `GET /api/settings`
- Auth: cookie.
@@ -196,7 +179,6 @@ separate section, but can still be updated by hand.
"updateAvailable": true,
"availableDigest": "sha256:...",
"pinned": false,
- "hidden": false,
"state": "running",
"composeFile": "/opt/stacks/web/compose.yaml",
"composeFileMissing": false,
@@ -227,8 +209,6 @@ Field notes:
- `pinned` — `true` if the image ref is in the `pinned` table ("Pin Version":
update indicator is suppressed and the container is grouped separately, but
a manual update is still allowed).
-- `hidden` — `true` if the container name is in the `hidden` table; the
- dashboard omits it (restore from Settings).
- `state` — Docker container state (`running`, `exited`, etc.).
- `composeFile` / `workingDir` — derived from
`com.docker.compose.project.config_files` /
diff --git a/client/src/Dashboard.jsx b/client/src/Dashboard.jsx
index 198d700..9c098a4 100644
--- a/client/src/Dashboard.jsx
+++ b/client/src/Dashboard.jsx
@@ -163,8 +163,8 @@ export default function Dashboard({ onPendingCountChange }) {
});
}, []);
- // Visible = not hidden. Pinned go to their own bottom section.
- const visible = useMemo(() => containers.filter((c) => !c.hidden), [containers]);
+ // Pinned go to their own bottom section; everything else is the main list.
+ const visible = containers;
const pinnedItems = useMemo(
() => visible.filter((c) => c.pinned).sort((a, b) => a.name.localeCompare(b.name)),
[visible]
@@ -324,7 +324,6 @@ export default function Dashboard({ onPendingCountChange }) {
container={container}
onSettled={handleSettled}
onPinChange={load}
- onHidden={load}
registerRunner={registerRunner}
/>
))}
@@ -349,7 +348,6 @@ export default function Dashboard({ onPendingCountChange }) {
container={container}
onSettled={handleSettled}
onPinChange={load}
- onHidden={load}
registerRunner={registerRunner}
/>
))}
diff --git a/client/src/api.js b/client/src/api.js
index 6f47fe6..51e1701 100644
--- a/client/src/api.js
+++ b/client/src/api.js
@@ -111,20 +111,6 @@ export function unpin(ref) {
return del(`/pin/${encodeURIComponent(ref)}`);
}
-// --- Hiding ---
-
-export function getHidden() {
- return get('/hidden');
-}
-
-export function hideContainer(name) {
- return post('/hide', { name });
-}
-
-export function unhideContainer(name) {
- return del(`/hide/${encodeURIComponent(name)}`);
-}
-
// --- Settings ---
export function getSettings() {
diff --git a/client/src/components/Header.jsx b/client/src/components/Header.jsx
index eef1f2f..793c0d0 100644
--- a/client/src/components/Header.jsx
+++ b/client/src/components/Header.jsx
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
-import { Link } from 'react-router-dom';
+import { Link, NavLink } from 'react-router-dom';
import { logout } from '../api.js';
import { useTheme } from '../hooks/useTheme.js';
@@ -69,6 +69,17 @@ export default function Header({ pendingCount = 0, onLoggedOut }) {
Diun Updater
{pendingCount > 0 && {pendingCount}}
+
{
- setHiddenError('');
- try {
- const data = await getHidden();
- setHidden(Array.isArray(data) ? data : []);
- } catch (err) {
- setHiddenError(err.message || 'Failed to load hidden containers');
- }
- }, []);
-
useEffect(() => {
setPinnedLoading(true);
loadPinned().finally(() => setPinnedLoading(false));
}, [loadPinned]);
- useEffect(() => {
- setHiddenLoading(true);
- loadHidden().finally(() => setHiddenLoading(false));
- }, [loadHidden]);
-
useEffect(() => {
getSettings()
.then((s) => setSettings(s))
@@ -101,22 +79,6 @@ export default function SettingsPage() {
[loadPinned]
);
- const handleUnhide = useCallback(
- async (name) => {
- setUnhidingName(name);
- setHiddenError('');
- try {
- await unhideContainer(name);
- await loadHidden();
- } catch (err) {
- setHiddenError(err.message || 'Failed to unhide container');
- } finally {
- setUnhidingName('');
- }
- },
- [loadHidden]
- );
-
return (
Settings
@@ -243,51 +205,6 @@ export default function SettingsPage() {
)}
-
- Hidden containers
- {hiddenLoading && (
-
- )}
-
- {!hiddenLoading && hiddenError && (
-
-
{hiddenError}
-
- Retry
-
-
- )}
-
- {!hiddenLoading && !hiddenError && hidden.length === 0 && (
-
-
No hidden containers.
-
- )}
-
- {!hiddenLoading && !hiddenError && hidden.length > 0 && (
-
- {hidden.map((name) => (
- -
-
- {name}
-
- handleUnhide(name)}
- disabled={unhidingName === name}
- >
- {unhidingName === name && }
- Unhide
-
-
- ))}
-
- )}
-
-
About
Diun Updater
diff --git a/client/src/styles/app.css b/client/src/styles/app.css
index 03fccba..f9a5873 100644
--- a/client/src/styles/app.css
+++ b/client/src/styles/app.css
@@ -1151,3 +1151,36 @@ a {
.version-value.is-available {
color: var(--color-success);
}
+
+/* ---------- Header nav (desktop only) ---------- */
+
+.header-nav {
+ display: none;
+}
+
+@media (min-width: 768px) {
+ .header-nav {
+ display: flex;
+ gap: 4px;
+ margin-right: auto;
+ margin-left: 20px;
+ }
+}
+
+.header-nav-link {
+ padding: 6px 12px;
+ border-radius: var(--radius-md);
+ color: var(--color-text-muted);
+ text-decoration: none;
+ font-size: 0.9rem;
+ font-weight: 600;
+}
+
+.header-nav-link:hover {
+ color: var(--color-text);
+}
+
+.header-nav-link.is-active {
+ color: var(--color-accent);
+ background: var(--color-elevated);
+}
diff --git a/server/src/containers-service.js b/server/src/containers-service.js
index 3bf82a1..bfe2abb 100644
--- a/server/src/containers-service.js
+++ b/server/src/containers-service.js
@@ -22,14 +22,12 @@ import { isUpdateAvailable, digestsEqual } from './reconcile.js';
* - returns the latest unresolved event row for a normalized ref, or
* undefined if there is none.
* @param {(normalizedRef: string) => boolean} params.isPinned
- * @param {(containerName: string) => boolean} [params.isHidden] - whether a
- * container is hidden from the dashboard. Defaults to "never hidden".
* @returns {{
* items: Array