diff --git a/docs/UX.md b/docs/UX.md index a2f9545..130e46e 100644 --- a/docs/UX.md +++ b/docs/UX.md @@ -27,15 +27,16 @@ Signed-in operators work inside a workspace. Primary sidebar navigation (desktop **Header:** stack name + optional runtime strip (health, container count, image, last refresh) -**Tabs:** Compose | Files | Docs | Environment | Secrets | Changes | Deployments +**Tabs:** Editor | Docs | Environment | Changes | History | Settings (Secrets and Deployments arrive later) No primary nav items for Containers / Images / Networks / Volumes. Repository pages have **Overview**, **Files**, **Docs** and **Changes** tabs: the same editor, docs and review views scoped to the whole repository, for files outside any stack (root README, `docs/`, shared `.env.example`). -Implemented in PR #3: **Editor** (explorer, tabs, problems, status bar), **Docs**, **Environment**, **Changes** and -**Settings** tabs. Secrets and Deployments arrive with PR #6 and PR #5. +Implemented: **Editor**, **Docs**, **Environment**, **Changes**, **History** (stack-scoped commits at the +fetched snapshot) and **Settings**. Draft counts appear on the Changes tab and stack header when work is outstanding. +Secrets and Deployments arrive with PR #6 and PR #5. ## Desktop diff --git a/docs/getting-started.md b/docs/getting-started.md index 9897586..35093a1 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -81,6 +81,7 @@ Each stack has these tabs: | **Docs** | Read the stack's Markdown files rendered (README first), follow links between them and edit them as drafts. | | **Environment** | Variables the Compose file references, whether `.env.example` documents them, env files it expects, and values that look like hard-coded secrets. Values are never shown. | | **Changes** | Every draft as a line-by-line diff against the fetched commit. Select files, commit with your Account Git identity, and push when ready. Discard drafts you don't want. | +| **History** | Commits that touched this stack at the last fetched snapshot. Open a commit to inspect the scoped file diffs. | | **Settings** | Rename the stack, change its Compose file or remove it (files and drafts are kept). | Files that look like secrets (`.env`, private keys, certificates, anything in a `secrets/` folder) are listed but diff --git a/docs/index.md b/docs/index.md index e905147..95630d2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -35,7 +35,8 @@ stack-manager looks after the Git source they are deployed from. stack-manager is in early development (0.x). Available today: sign-in, workspaces, encrypted credentials, repository connections, stacks, dashboard and settings, the editor with drafts, docs, the environment inventory and change review. -Coming in v0.5.0: committing drafts, safe push and Git history. After that on the [roadmap](plans/MVP_PLAN.md): +Stack-scoped Git history is on each stack's History tab. Draft commit and safe push are on Changes. Next on the +[roadmap](plans/MVP_PLAN.md): deploying only the stacks a commit changed (through Portainer and others), then optional read-only runtime feedback. ## Documentation diff --git a/src/app/styles/source.css b/src/app/styles/source.css index 69998d9..eba075a 100644 --- a/src/app/styles/source.css +++ b/src/app/styles/source.css @@ -1197,6 +1197,99 @@ select.compact-select { font-size: var(--text-xs); font-weight: 600; white-space: nowrap; + text-decoration: none; +} + +.page-title-row .draft-badge { + margin-left: 0; +} + +/* ---------- Stack history ---------- */ +.history-list { + list-style: none; + margin: 0; + padding: 0; + display: grid; + gap: var(--space-3); +} + +.history-item { + border: 1px solid var(--border); + border-radius: var(--radius); + background: var(--surface); + overflow: hidden; +} + +.history-row { + display: flex; + align-items: flex-start; + gap: var(--space-3); + width: 100%; + padding: var(--space-3) var(--space-4); + border: 0; + background: transparent; + color: inherit; + text-align: left; + cursor: pointer; +} + +.history-row:hover, +.history-row:focus-visible { + background: var(--surface-2); +} + +.history-row-main { + display: grid; + gap: var(--space-1); + min-width: 0; + flex: 1; +} + +.history-subject { + font-weight: 600; + overflow-wrap: anywhere; +} + +.history-meta { + display: flex; + flex-wrap: wrap; + gap: 0 0.25rem; +} + +.history-files, +.history-files-empty { + margin: 0; + padding: 0 var(--space-4) var(--space-3); + padding-left: calc(var(--space-4) + 1.5rem); + list-style: none; + display: grid; + gap: var(--space-1); + font-size: var(--text-sm); +} + +.history-files li { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--space-2); + min-width: 0; +} + +.history-files .mono { + overflow-wrap: anywhere; +} + +.history-detail { + border-top: 1px solid var(--border); +} + +.history-detail-title { + margin: 0; + font-size: var(--text-md); +} + +.history-file-diff + .history-file-diff { + border-top: 1px solid var(--border); } /* Flat lists (grouped by repository only, or not at all) have no folder labels above their first row. */ diff --git a/src/app/w/[workspaceId]/stacks/[stackId]/history/page.tsx b/src/app/w/[workspaceId]/stacks/[stackId]/history/page.tsx new file mode 100644 index 0000000..91c7677 --- /dev/null +++ b/src/app/w/[workspaceId]/stacks/[stackId]/history/page.tsx @@ -0,0 +1,19 @@ +import type { Metadata } from 'next'; +import { getContainer } from '@/server/container'; +import { HistoryView } from '@/ui/source/history-view'; +import { orNotFound } from '../../../../../_lib/session'; + +export const metadata: Metadata = { title: 'History' }; + +export default async function StackHistoryPage({ + params, +}: { + params: Promise<{ workspaceId: string; stackId: string }>; +}) { + const { workspaceId, stackId } = await params; + const { stacks } = getContainer(); + const stack = await orNotFound(stacks.get(workspaceId, stackId)); + return ( + + ); +} diff --git a/src/app/w/[workspaceId]/stacks/[stackId]/layout.tsx b/src/app/w/[workspaceId]/stacks/[stackId]/layout.tsx index 480123e..7a67ea4 100644 --- a/src/app/w/[workspaceId]/stacks/[stackId]/layout.tsx +++ b/src/app/w/[workspaceId]/stacks/[stackId]/layout.tsx @@ -1,4 +1,4 @@ -import { Folder, FolderGit2, GitBranch, GitCommitHorizontal, Hourglass } from 'lucide-react'; +import { Folder, FolderGit2, GitBranch, GitCommitHorizontal, Hourglass, PencilLine } from 'lucide-react'; import Link from 'next/link'; import { getContainer } from '@/server/container'; import { EmptyState } from '@/ui/primitives/empty-state'; @@ -24,6 +24,7 @@ export default async function StackLayout({ stacks.listForRepository(workspaceId, stack.repository.id), ]); const base = `/w/${workspaceId}/stacks/${stackId}`; + const draftLabel = `${drafts} draft${drafts === 1 ? '' : 's'}`; return (
@@ -37,6 +38,18 @@ export default async function StackLayout({ /> } title={stack.name} + status={ + drafts > 0 ? ( + +