View log navigated away to a separate log page. Open the log of the run in a
dialog next to the run list instead, with Stop (for a running build) and Close
at the bottom. The tag cell and the action menu both open the dialog, Rerun now
refreshes the list instead of following the new run, and the terminal lives in
a reusable component that refits itself when the dialog is resized.
Verification:
- bun run build
- bun run lint
- Opened a finished run in a headless browser: the dialog shows the stored log
and only Close.
- Opened a running run: the log streams, the footer has Stop, and clicking it
disables the button.
The builder runner API returns UUID strings for the runner and builder ids, but
the interfaces declared them as numbers and the duration chart sample followed.
Verification:
- bun run build
- bun run lint
The status message tooltip was a single nowrap line, so long build errors
spread far beyond the table. It was also absolutely positioned without
offsets, so its hidden box escaped the scroll container and extended the
document height, which pushed the run list pagination away from the bottom of
the window.
Give it a max width, allow wrapping and break long tokens, and position it
fixed so the hidden element stays out of the scrollable overflow.
Verification:
- bun run build
- bun run lint
- Opened the v0.1 error in a headless browser: the message wraps to 6 lines
within 448px; with a 500px tall viewport the document no longer scrolls and
the pagination stays at the bottom.
Creating a build followed the new run's log page, which threw away the overview
the user came from. Close the dialog and refresh the run list instead, so the
new run shows up in place.
Replace the plain Rerun/Stop action text with a dropdown offering the run log
and, depending on the status, Rerun or Stop.
Verification:
- bun run build
- bun run lint
- Clicked Build then Create in a headless browser: the dialog closes, the URL
stays the same and the list refreshes; the action menu lists View log and
Rerun for a finished run.
Clicking the tag name or the image reference showed a floating "Copied"
tooltip above the trigger, which was hard to read and wide. Show the
confirmation where the copy happened instead: next to the tag name, or inside
the image reference block on its right side, for 1.6 seconds.
The reference block now shows just the image reference (no docker pull/helm
pull verb) and uses a narrower width. The page level tooltip element and its
ids are gone.
Verification:
- bun run build
- bun run lint
- Clicked both copy targets in a headless browser: the inline "Copied" marker
appears and disappears while the page keeps the same URL.
The tag list parsed config_raw, sbom and vulnerability results with JSON.parse
without guarding the empty strings the API returns for artifacts that carry no
config (raw blobs, for example), which crashed the page with "Unexpected end of
JSON input". It also passed an undefined distro name into the distro helpers,
which called trim() on it.
Parse the encoded artifact fields through a helper that falls back on empty or
invalid JSON, guard the manifest fields read by skipManifest, tolerate a null
artifact list, and make the distro helpers return an empty name instead of
throwing.
Verification:
- bun run build
- bun run lint
- Opened the affected tag list in a headless browser: the row renders with "-"
for OS/ARCH and 0 C/H/M/L instead of crashing.
The build dialog used hand written inputs and textarea with legacy styles and
swapped error classes plus an inline icon for validation. Use the shared
Input, Textarea and Label components and drive the error state with
aria-invalid, so the dialog follows the same look as the rest of the forms.
Verification:
- bun run build
- bun run lint
- Opened the dialog in a headless browser and checked the invalid state.
Add an info tooltip to the cron expression and the OCI tag template field:
the first documents the five-field cron format and the time zone it is
evaluated in, the second lists the Go template variable and the sprig helpers
the builder renders it with. Both inputs use the shared Input component now.
Verification:
- bun run build
- bun run lint
The picker only offered the first page of repositories sorted by name. Turn
it into a searchable popover that queries the provider list endpoint with the
typed name, so repositories past the first page stay reachable, and ask for
the most recently pushed repository first. The repository and branch pickers
now take half a row each, below the provider and organization.
Verification:
- bun run build
- bun run lint
- Opened the builder page and confirmed the picker filters while typing.
Listing code repositories goes through the provider API and only a token
credential can authenticate those calls, so the source code credential dialog
no longer offers SSH keys or username and password. Drop the credential type
select, the fields it switched between, and the translations they needed.
Verification:
- bun run build
- bun run lint
cmdk always renders data-selected (true or false) on its items, while the
data-selected variant is an attribute presence selector, so every command
item matched the highlight styles. Use the exact-value variant so only the
hovered or keyboard selected item is highlighted.
Verification:
- bun run build
- bun run lint
Show how long the recent builds took above the run list: the chart reads the
last 100 runs of the builder, plots the elapsed time of the finished ones and
marks the latest duration in the header. It is drawn as plain svg so no chart
dependency is needed, and hovering a point shows the tag, the duration and
the time of that build.
Verification:
- bunx tsc --noEmit
- bun run lint
- bun run build
The page listened to popstate and called navigate(backTo) with an empty
`back_to`, and react-router resolves an empty path to the current pathname
with an empty search, pushing a new entry. Pressing back therefore stayed on
the builder page and dropped every query parameter. The mount effect that
re-pushed the current query had the same problem: it added a duplicate
history entry.
Drop both; `back_to` is still used by the Cancel button, and the browser back
button now undoes the navigation that opened the page.
Verification:
- bunx tsc --noEmit
- bun run lint
- bun run build
A repository exposed two tabs that only differed by whether a builder was
configured: "Builder" opened the setup form and "Runs" opened the build
list. Merge them into one "Builder" tab: it shows the setup form while the
repository has no builder and the run list once a builder exists.
- add RepositoryTabs, the tab bar shared by the repository pages
- add pages/Builder/index.tsx, which picks the setup form or the run list
from the repository builder and swaps to the run list after a save
- render the setup form inside the repository chrome when embedded
- point the configure/update/back links at the merged route and keep
/repository/runners as an alias for existing bookmarks
Verification:
- bunx tsc --noEmit
- bun run lint
- bun run build
The builder belongs to a repository, so the namespace level entry between
the members and the daemon task sections duplicated the repository tab.
Remove the section entry, its daemon gate and the builder specific link,
and stop mapping the builder route to the removed section.
Verification:
- bunx tsc --noEmit
- bun run lint
- bun run build
The web app only uses `size` and `orderBy`, and `lodash` is the CommonJS
build that the bundler cannot tree-shake. Import the helpers by name from
`lodash-es` instead, drop `lodash` and declare `@types/lodash-es` (it was
only reachable through bytemd before).
Verification:
- bunx tsc --noEmit
- bun run lint
- bun run build
The list offered an Oci Repo Count column nobody acted on, an action column
whose only entry opened the setup builder — reachable from the repository
tabs now — and a timestamp that read as nine hours old because the API sends
UTC wall-clock strings and dayjs parsed them as local time.
- replace the count and action columns with the last update time, rendered
through RelativeTime so the timezone is handled once
- sort by pushed_at descending and page twenty at a time, with the page size
as a constant instead of the global settings value
- drop the forced 100rem page width: the app layout already sizes the content
column, so the pages overflowed it and their justify-between rows pushed
the actions past the right edge
- give the sync button the same gutter as the namespace page's create button,
and let the pagination component own its container
Verification:
- cd web && npx tsc --noEmit
- cd web && npx oxlint
- cd web && npx vite build
The form mixed three interaction styles: popovers with a cmdk search list,
plain selects and hand-styled inputs. Picking a provider could leave two
options looking highlighted, an empty list opened an empty popup that
rendered as a thin grey line, and several fields had no dark mode support.
Make every picker a Select and calm the form down:
- provider, organization, repository and branch — plus the cron and merge
branch pickers — are Selects now; the popup is only rendered when there is
something to choose, and the branch pickers refresh their list when opened
because branches are no longer synced
- provider options show their display name, an unavailable provider disables
the organization and repository pickers, and its hint opens the credential
dialog in place instead of navigating away
- the OCI repository picker lists the repository name without the namespace
prefix, the way the repository is referred to elsewhere
- the section descriptions move into info tooltips next to the headings
- clone depth uses the shared input, round-trips through
code_repository_clone_depth, clamps to a non-negative integer and explains
that 0 clones the full history
- the self-managed repository block uses the shared inputs, keeps the token
masked and gives the fields room to breathe
- CommandItem gets a pointer cursor, which fixes every cmdk list, and the
setup page drops the forced 100rem width that pushed it past the viewport
Verification:
- cd web && npx tsc --noEmit
- cd web && npx oxlint
- cd web && npx vite build
The repository pages carried their own tab strip, and the sidebar repeated
the same entries under the repository node, so the same navigation existed
in two places and neither was complete: the Setup builder page was only
reachable from the code repository list, which no longer links to it.
- add /namespaces/:namespace/repository/builder rendering the setup builder,
and turn the repository tabs into Summary | Builder | Runs | Tag list; the
former "Builder" tab, the runner list, becomes "Runs"
- remove the repository level from the sidebar: its entries are the page
tabs now, and "Repository list" stays highlighted while browsing into a
repository
- drop NamespaceTabs, the duplicate header strip on the namespace pages
- resolve entity ids as opaque UUID strings: interface fields are strings
now and the pages that parsed ids with parseInt stopped truncating them
(a UUIDv7 starts with "1a1...", so parseInt returned 1)
- add useNamespaceId: a page opened without ?namespace_id — a bookmark or a
hand written URL — looks the id up from the namespace in the route instead
of requesting /namespaces/null or /namespaces//
- the tag page: title is Tags, its search box matches the namespace one, and
it skips the request while the namespace is unresolved
Verification:
- cd web && npx tsc --noEmit
- cd web && npx oxlint
- cd web && npx vite build
The credential dialog used a w-fit trigger, so the popup inherited the width
of the currently selected label ("Access token"), and the explicit min-w-0
on the popup removed the 9rem floor that select-content normally keeps. The
longest option, "Username and password", was therefore clipped by the
popup's overflow-x-hidden.
Let the trigger span the dialog and keep the default min width of the popup.
Verification:
- cd web && npx tsc --noEmit
- cd web && npx oxlint
- cd web && npx vite build
The code repository entry sat next to namespaces even though it holds no
content: it only configures the source builds clone from. Its provider
submenu came from the OAuth2 login flags, so turning OAuth off left an
empty menu entry and a blank page, and there was no way to supply an access
token at all.
- move the entry into the Settings group and drop the provider submenu; the
providers are now cards on the page, so listing them twice only added a
third level
- rebuild /coderepos as one card per provider showing the linked account,
the credential type and the last sync result, with actions to add or
update a credential, resync, clear it, or open the repository list
- add CredentialDialog for the token, SSH key and username/password types,
with the OAuth binding offered as an alternative that still needs a token
for syncing
- read the sync state of /coderepos/{provider} from the providers response
instead of issuing a second request, and point the builder setup provider
combobox at the credential settings when nothing is configured
- extend ICodeRepositoryProviderItem with the new fields and add the
zh-CN/en-US strings
Verification:
- cd web && npx tsc --noEmit
- cd web && npx oxlint
- cd web && npx vite build
The legacy pages still used scattered Tailwind colors (purple, blue,
indigo) as interactive accents, so the app drifted away from the single
neutral `--primary` accent of the design system. Replace them with tokens:
- purple primary buttons -> bg-primary / text-primary-foreground
- gray secondary buttons -> bg-secondary
- indigo active tabs -> border-primary / text-foreground
- purple and indigo focus rings -> focus:ring-ring
- blue switches -> peer-checked:bg-primary, with the knob switched to
primary-foreground while checked so it stays visible in dark mode
- blue checkboxes -> accent-primary
- blue links and the Dockerfile editor selection -> text-primary and
selection:bg-foreground
Verification:
- cd web && npx oxlint
- cd web && bun run build
The legacy page shells hardcoded `bg-white` without a dark variant, so
the whole page rendered white in dark mode. Use the `bg-background`
token, matching the pages that were already migrated.
Verification:
- cd web && npx oxlint
- cd web && bun run build
"Deactive" is not a valid English word; the standard antonym of "Active"
is "Inactive". Restore the enum value, regenerate the go-enum output,
update the CLI test expectation, sync the generated swagger enum and
x-enum-varnames, and fix the web UI request payload and label.
Note: persisted rows and API clients using the old "Deactive" value must
switch to "Inactive"; no data migration is included.
Verification:
- go build -tags "netgo,timetzdata,exclude_graphdriver_btrfs,containers_image_openpgp" ./...
- go vet -tags "netgo,timetzdata,exclude_graphdriver_btrfs,containers_image_openpgp" ./pkg/api/enums/... ./cmd/cli/...
- CI_DATABASE_TYPE=turso go test -tags "netgo,timetzdata,exclude_graphdriver_btrfs,containers_image_openpgp" ./pkg/server/validators/...
Load Nunito (variable, normal and italic) from Fontsource and put it
first in the `--font-sans` stack, keeping Geist as a fallback so
characters missing from Nunito still render.
Also bump the sidebar `sigma` wordmark from `text-sm` to `text-lg` so
the wordmark reads more clearly next to the 32px logo mark.
Verification:
- bun run lint
- bun run build
Reorder imports across web/src into two blocks separated by a blank line:
third-party packages first, then this repository's @/ and relative modules,
each sorted alphabetically. Document the convention in AGENTS.md.
Verification:
- web: `npx tsc --noEmit`, `npx oxlint`, `npx vite build`
Drop the shared minimum popup width on the create and update namespace
visibility and size-limit unit selects so each popup matches its trigger.
Verification:
- web: `npx tsc --noEmit`, `npx oxlint`, `npx vite build`
Add a SquarePen icon for Update and a Play icon for Run in the four GC
action menus, and tidy the page formatting.
Verification:
- web: `npx tsc --noEmit`, `npx oxlint`, `npx vite build`
- right align the namespace count and status columns, and rename the
namespace header to "Namespace count"
- render last login and created at with RelativeTime
- replace the inline update button with a centered three-dot menu whose
Deactive action calls PUT /users/{id} to deactivate the user
- drop the status field from the update dialog, disable the role select
for the signed in user, and omit role/status when updating self
Verification:
- web: `npx tsc --noEmit`, `npx oxlint`, `npx vite build`
Open select popups below the trigger instead of covering it, show a
pointer cursor on select options, give options more vertical room, and
use a not-allowed cursor for disabled selects and menu items.
Verification:
- web: `npx tsc --noEmit`, `npx oxlint`, `npx vite build`
Rebuild the sidebar menu as a context-driven hierarchy so every
collapsible group has real children:
- add a Home entry and list namespaces under Namespaces, merging the
former hot-namespace group; the active namespace is always shown and
expanded even when it is not hot
- nest the namespace sections (summary, repositories, members, daemon
tasks, webhooks) and the active repository sections (summary, tags,
builder) under the current branch, using SidebarMenuSub so children
share a vertical guide line
- expose the enabled code repository providers (github, gitlab) under
Code Repository and fall back to a plain link when none is enabled
- drive expansion and highlighting from the route only, highlighting the
selected leaf instead of its container rows
- add a Base UI collapsible primitive, align the sidebar menu metrics
with the content lists (h-9/text-sm items, h-8 sub items, sidebar-accent
active state), use a neutral gray for the sidebar text both when idle
and selected, and drop the unused --sidebar-active token
- add the menu.allNamespaces message
Also polish the settings user list: un-bold the header and username, show
last login and created at with RelativeTime, and replace the inline update
button with a centered three-dot dropdown.
Verification:
- web: `npx tsc --noEmit`
- web: `npx oxlint`
- web: `npx vite build`
Reformat the page, switch relative imports to the @ alias, replace the
inline update/delete buttons with a dropdown row-actions menu, and use
exponentiation for the TiB/GiB/MiB size conversion.
Verification:
- cd web && bunx tsc --noEmit
- cd web && bun run lint
JavaScript bitwise shifts operate on 32-bit integers, so `1 << 40` evaluates
to `1 << 8` (256) instead of 1 TiB, corrupting TiB size limits and the
calcUnit TiB branch. Replace the shifts with exponentiation.
Verification:
- cd web && bunx tsc --noEmit
- cd web && bun run lint
- Update go.mod/go.sum (grype, syft, aws-sdk-go-v2, distribution,
gin contrib, validator and others).
- Update web/package.json and web/bun.lock (shikijs, react-router-dom,
lucide-react, oxlint, esbuild, vite and others).
- Ignore the local `.delta/` directory in .gitignore.
- Add a shared NamespaceTabs component and use underline tabs across the
namespace summary, repository, member, daemon task and webhook pages.
- Redesign the pagination footer with theme tokens and a page indicator,
and give previous/next proper pointer and disabled states.
- Align the daemon task and webhook table headers with the namespace list
style, drop bold text, and use a muted header color.
- Convert the namespace member list to the shadcn Table component and
match the search/create toolbar height.
- Modernize the webhook create dialog (Input, Label, Switch, two-column
events) and unify button and input styles.
- Make the namespace summary markdown editor fill the available height.
- Stay silent on 404/empty responses for the namespace summary, member
and webhook requests instead of showing an error toast.
- Add standard `appearance` declarations and update deprecated Tailwind
classes to clear editor warnings.
Verification: bunx tsc --noEmit; bunx oxlint; bun run build (web)
Turn on the react, react-hooks, suspicious and perf rule sets in oxlint and
fix the 2665 diagnostics they report instead of muting the rules:
- validation/limit state is derived during render instead of being synced
in an effect (namespace/repository/tag limits, size limit + unit,
username/email/password checks in Menu, Users, Webhook and Member)
- refetch triggers are gone: mutation handlers now call the memoized fetcher
directly (Namespace list and members, Menu token timer) instead of bumping
a `refresh` state that only existed to re-run an effect
- data fetchers (fetchNamespace/fetchTags/fetchRepository/fetchWebhook/
fetchMembers/logout) are memoized with useCallback and their effects use
correct dependency arrays; the async profile seeding uses an override state
instead of an effect
- fix 39 shadowed variables (resp/err/*ListData/configData/requestUrl/...),
use stable list keys instead of array indexes, hoist `cutDigest` to module
scope and memoize the ToggleGroup context value
- replace the ported HTML5 email pattern with a zod based `isEmail` helper
(zod 4, `z.email()`), dropping the control-character regex
- use-mobile seeds its state lazily and Shiki derives the highlighted html
with useMemo
.oxlintrc.json pins the setup: plugins react/typescript/unicorn/oxc,
correctness as error, suspicious/perf as warn, `_`-prefixed names ignored by
no-unused-vars, and react-in-jsx-scope off because the app uses the React 19
automatic JSX runtime (that rule only applies to React < 17).
Verification: web: `bun run lint` (0 warnings, 0 errors, no
oxlint-disable directives left in src); `tsc --noEmit`; `vite build`;
unused-import scan reports 0.
Fix the diagnostics that oxlint reports on the codebase instead of
silencing the rules:
- unused event parameters (41): `onClick={e => ...}` -> `onClick={() => ...}`
and `.catch(err => ...)` -> `.catch(() => ...)` where the argument was
never used
- unused state members: keep only the half that is used
(`const [sortOrder] = useState(...)`, `const [, setUserObj] = ...`), and
drop the sort state in CodeRepository/List that was fully unused
- unused props/locals: drop `index` from TableItem/TableItemDropdown (plus
interface and call sites), `namespace`/`repository` from Tag TableItem,
two dead `const user = ...` in Login/Callback and the unused `u` URL in
Webhook/List, along with the imports that became unused (IUserSelf, IOrder)
- one-line effects (10): `useEffect(() => { cond && setX() }, [dep])` ->
explicit `if (cond) { setX(); }`
- utils/index.ts: keep the HTML5 email pattern as-is with a scoped
`oxlint-disable-next-line no-control-regex` and a reason
Add .oxlintrc.json to pin the setup: correctness rules are errors,
no-unused-vars ignores `_`-prefixed names, browser/es2024 globals,
dist/node_modules/public ignored. The rule set matches the previous
defaults, so nothing is disabled to hide a warning.
Verification: web: `bun run lint` (0 warnings, 0 errors); `tsc --noEmit`;
`vite build`; unused-import scan reports 0.
Swap every headlessui usage for the shadcn/base-ui components that are
already bundled in the app, then drop the dependency:
- menus (8): Menu/MenuButton/MenuItems/MenuItem + Transition -> DropdownMenu;
role checks that guarded the original clicks are preserved as
DropdownMenuItem `disabled`
- dialogs (14): Dialog/DialogPanel/DialogTitle + Transition* -> Dialog,
DialogContent, DialogTitle, DialogDescription, DialogFooter and Button
- listboxes (6): Listbox -> Select; the multiple-selection one becomes a
DropdownMenu with DropdownMenuCheckboxItem
- comboboxes (9): Combobox -> Popover + Command, wiring the existing search
state into CommandInput and keeping shouldFilter={false} for server-side
search
- remove @headlessui/react from package.json/bun.lock
Also included:
- drop imports that became unused (Table/Button in Builder/RunnerList,
Separator, useSidebar, dayjs, Link/useParams, Check/ChevronsUpDown and the
leftover shadcn imports in the webhook pages)
- replace Tailwind v4 arbitrary sizes that have spacing-scale equivalents
(w-[350px] -> w-87.5, h-[36px] -> h-9, after:top-[2px] -> after:top-0.5, ...);
h-[18.4px] is intentionally kept since 4.6 is not a multiple of 0.25
- fix the webhook log delete-confirm label (was "Resend")
- add oxlint with `lint` and `lint:fix` scripts
Verification: web: `tsc --noEmit`; `vite build`; `oxlint` (0 errors,
72 pre-existing warnings); scripted scan reports 0 unused imports and no
remaining @headlessui imports.
Follow up the shadcn/ui migration with a round of layout and UX fixes:
- layout: unify the horizontal padding of the page header, page toolbars,
table headers/cells and pagination on `px-4`, so titles, search boxes and
the first/last table columns share one left/right baseline while the table
itself stays full width.
- sidebar: stop the SidebarProvider wrapper (`w-full`) from squeezing the
page content, enlarge the menu rows, lighten the active background through
a new `--sidebar-active` token, drop the bold active label, and always show
a pointer cursor for buttons and menu items.
- user menu: fold API docs, theme and language into the bottom dropdown,
persist username/email on login, callback and token refresh, fall back to
"sigma" when no username is known, and expose a Login entry for guests.
- tables: replace the row action dropdown with inline update/delete icon
buttons (delete in red), right align the Action column, only render sort
arrows for the active sort, keep namespace/repository names readable on
hover, and render created/updated columns as relative times.
- components: add `RelativeTime` (dayjs relativeTime + tooltip) and the
`Field`/`FieldLabel` primitives, rebuild the login page on top of them,
and bump the shared button/input/select/toggle/tabs presets to h-9.
- home: lay the summary cards out in a responsive grid instead of a fixed
1600px wide row that overflowed the viewport.
- utils: merge the short helper modules into `utils/index.ts` (trimHTTP,
calcUnit, Regex, Tooltip) and `utils/request.ts` (axios interceptors plus
token refresh), leaving only index/request/distros.
Verification:
- `npx tsc --noEmit`
- `npx vite build`
Add login failure rate limiting with per-username tracking, supporting both delay-based throttling and lockout via configurable backend.
Update all sample configs, Helm templates, and documentation to include the new auth loginRateLimit settings.
Verification:
All config files, Helm charts, and docs are updated to match the new configuration schema.
Update direct and indirect Go module versions, including aws-sdk-go-v2,
goose, ClickHouse, Azure SDK and golang.org/x/exp, and refresh the web
toolchain (react 19.3, vite 8.3, typescript 7, shadcn, tailwind).
Switch the web Vite `@` alias from `__dirname` to `import.meta.dirname`
to align with the ESM config runtime.
Verification: not run (dependency-only change).
Complete migration from legacy React toastify/headlessui to shadcn/ui components, update Tailwind CSS setup, add path aliases, and replace old icon libraries with lucide-react. Also update dependencies to modern versions and clean up unused files and styles.
Update go version from 1.26 to 1.27 across all workflows, dockerfile, and project configs. Switch uuid usage to stdlib package instead of external google/uuid. Bump traefik, bun, and various golang/x dependencies versions. Clean up unused direct uuid dependency.
Verification:
go test ./pkg/utils/uuid/...