Skip to content

feat(containers): add compose editor tab for compose-managed containers#5

Open
mkaltner wants to merge 73 commits intomainfrom
feat/container-compose-editor
Open

feat(containers): add compose editor tab for compose-managed containers#5
mkaltner wants to merge 73 commits intomainfrom
feat/container-compose-editor

Conversation

@mkaltner
Copy link
Copy Markdown
Owner

@mkaltner mkaltner commented Mar 13, 2026

Summary

Adds a Compose tab to the individual container detail view. When a container belongs to an Arcane-managed project, the tab loads the relevant compose file — whether defined directly in the root compose or in an included sub-file — and allows viewing and editing it without leaving the container view.

Fixes: getarcaneapp#1554


Changes

Backend (types/container/container.go)

  • Added ComposeInfo type to structure Docker Compose metadata
  • Modified NewDetails to extract compose information from container labels:
    • com.docker.compose.projectprojectName
    • com.docker.compose.serviceserviceName
    • com.docker.compose.config-filesconfigFile
    • Additional working directory and project directory metadata
  • Included composeInfo field in container Details response when labels exist
  • Performance improvement: Heavy label processing now happens backend-side in Go

Frontend Types (container.type.ts)

  • Added ComposeInfo interface matching backend structure
  • Extended ContainerDetailsDto with optional composeInfo field

Internationalization (messages/en.json)

Added i18n strings for all hardcoded text:

  • container_compose_gitops_managed_title: "GitOps Managed — Read Only"
  • container_compose_gitops_managed_description: Parameterized alert description
  • container_compose_editing_info: Editing info banner with file/project/service context
  • container_compose_save_success: Success toast message
  • container_compose_save_failed: Error toast message
  • container_compose_view_project: Button label

+page.ts

  • Simplified project loading: uses container.composeInfo?.projectName instead of direct label access
  • Backend-provided structure eliminates redundant label parsing

+page.svelte

  • Uses container.composeInfo from API response instead of accessing labels directly
  • Derives composeServiceName from structured composeInfo object
  • Conditionally shows Compose tab when composeInfo exists
  • Still performs client-side YAML parsing to determine root vs include file (project-specific logic)
  • Fixed Svelte 5 reactivity pattern to properly track prop changes

ContainerComposePanel.svelte

  • Replaced all hardcoded strings with i18n message calls (m.*)
  • Fixed Svelte 5 antipattern: removed unconditional effect that updated state
  • Proper reactivity: only updates composeContent when source actually changes
  • Uses @html for parameterized messages with embedded formatting

Behavior

  • Service in root compose.yml — tab shown, editable, saves via updateProject
  • Service in an include sub-file — tab shown, correct sub-file loaded (e.g. ollama/compose.yml), saves via updateProjectIncludeFile
  • Service not found in any file — tab hidden
  • No compose labels — tab hidden (backend doesn't include composeInfo)
  • GitOps managed — tab shown, read-only, localized alert displayed

Code Review Improvements

Backend processing: Compose metadata extraction moved to Go (faster, reduces client load)
i18n coverage: All UI strings now localized via paraglide messages
Svelte best practices: Removed state-updating effects per Svelte 5 recommendations
Simplified frontend: Uses structured API response instead of raw label parsing


PR Checklist

  • Code builds successfully in development environment (./scripts/development/dev.sh start)
  • Frontend loads at http://localhost:3000 with hot reload working
  • Backend responds at http://localhost:3552 with hot reload working
  • No linting errors: just lint frontend
  • Formatting passes: just format frontend
  • Commit messages follow conventional format ✅
  • PR description explains the change and why it is needed ✅

Manual Testing

  • Compose tab appears for a container in a single-file Arcane project
  • Compose tab appears and loads the correct sub-file for a container in an include:-based project (e.g. ai-ollama-1ollama/compose.yml)
  • Compose tab is hidden for containers not associated with any Arcane project
  • Compose tab is hidden when the service is not defined in any known compose file
  • Save works correctly for root compose files
  • Save works correctly for include sub-files
  • GitOps-managed projects show read-only alert and disable the save button
  • All UI text appears in English (i18n strings loaded correctly)
  • Backend composeInfo field appears in API response when compose labels exist

AI Disclosure

AI Tool: Claude Code (Anthropic)
Assistance Level: Significant — implementation was AI-generated with human direction, review, and testing oversight per AI_POLICY.md

mkaltner and others added 7 commits March 13, 2026 18:13
…iners (getarcaneapp#1554)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ainerComposePanel component

- Fix project lookup: search by name via getProjectsForEnvironment then fetch by ID,
  instead of incorrectly passing project name to getProject (which expects an ID)
- Move project loading to +page.ts data loader (proper SvelteKit pattern)
- Extract inline CodePanel usage into dedicated ContainerComposePanel.svelte component
- Support editable compose file with save via projectService.updateProject
- Show read-only alert for GitOps-managed projects
- Use untrack() on initial state to satisfy Svelte 5 reactive reference rules
- Link back to project page via ExternalLink button

Closes getarcaneapp#1554
…-compose

When a project's compose.yml uses `include:` directives (a meta/wrapper compose),
the service definition lives in a sub-file — not the root compose. Showing and editing
the root compose from a container view is misleading and not useful.

Parse the project's composeContent with the yaml library to check whether
`services.{serviceName}` exists directly in the top-level services block.
If not, suppress the Compose tab entirely for that container.

Also tightens the info banner copy to drop the redundant
'Changes to this file affect all services' sentence (now always accurate since
we only show direct-service composes).
…ived not a stored function

$derived((): boolean => {...}) stores a function as the derived value; Svelte 5
doesn't track reactive reads (project, composeServiceName) when the stored function
is called later from inside tabItems. Use an IIFE inside $derived instead so the
boolean is computed directly and reactivity is properly tracked.
…including sub-files)

When a container belongs to a project with `include:` directives, find the
include file that directly defines the service and show/edit that instead of
the root wrapper compose.

- serviceComposeSource derived: scans root compose, then includeFiles[], to find
  which file has `services.{serviceName}` defined
- Shows tab for both root-defined and include-file-defined services
- Hides tab only when the service isn't found in any file
- ContainerComposePanel now accepts optional `includeFile` prop
  - Displays includeFile.content instead of project.composeContent
  - Saves via projectService.updateProjectIncludeFile (relativePath + content)
  - Falls back to updateProject for root compose
  - fileTitle shows the actual file name (e.g. ollama/compose.yml)
  - fileId scoped per include file so editor state doesn't collide
@mkaltner mkaltner force-pushed the feat/container-compose-editor branch 2 times, most recently from 1256f3e to 788ba6c Compare March 13, 2026 22:55
kmendell and others added 5 commits March 13, 2026 17:57
- Add ComposeInfo to backend container Details response with Docker Compose metadata
- Extract compose project/service info from labels in Go (backend-side processing)
- Add i18n strings for all hardcoded text in ContainerComposePanel component
- Simplify frontend logic to use composeInfo from API instead of label parsing
- Fix Svelte reactivity pattern to properly track source content changes
- Remove redundant Svelte effect antipattern per code review feedback

Addresses code review feedback to move heavy lifting to backend and ensure
proper internationalization coverage.
@mkaltner mkaltner force-pushed the feat/container-compose-editor branch from c19f751 to 678f8f2 Compare March 14, 2026 12:32
dependabot bot and others added 15 commits March 14, 2026 11:08
…etarcaneapp#2056)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
….14.0 in /backend (getarcaneapp#2057)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…getarcaneapp#2058)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…etarcaneapp#2063)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Resolved conflict in types/container/container.go by keeping both:
- ComposeInfo struct (from feature branch)
- SummaryGroup struct (from upstream main)

Both structs serve different purposes and can coexist without conflict.
…getarcaneapp#2059)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…nd (getarcaneapp#2061)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…etarcaneapp#2060)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
… 1.16.2 in /cli (getarcaneapp#2054)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…etarcaneapp#2055)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1. Add dirty tracking to prevent unsaved edits from being lost on data refresh
   - Track isDirty state based on composeContent vs sourceContent
   - Only update editor when source changes AND no unsaved edits exist
   - Reset isDirty flag after successful save

2. Add 'Viewing' vs 'Editing' label for read-only mode
   - Added container_compose_viewing_info i18n string
   - Conditionally display 'Viewing' when isReadOnly is true
   - Maintains consistent i18n coverage

3. Remove unused saveError state variable
   - Errors are already surfaced via toast notifications
   - Simplifies component state management

4. Add explicit pagination limit to project lookup
   - Pass pagination: { page: 1, limit: 100 } to getProjectsForEnvironment
   - Ensures compose tab appears even with many projects (>20)
   - Prevents silently missing projects beyond default page size

Addresses automated review feedback from greptile-apps[bot].
kmendell and others added 30 commits March 14, 2026 20:59
…aneapp#2064)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Kyle Mendell <ksm@ofkm.us>
- Move success toast before invalidateAll() to avoid misleading error on cache refresh failure
- Pass actual root compose filename instead of hardcoding compose.yml
Co-authored-by: Felix <gitea@felixnuesse.de>
Co-authored-by: Kyle Mendell <ksm@ofkm.us>
Extract duplicated compose editor UI (GitOps alert, info banner, save
button, view project link, toast+invalidateAll logic) into a reusable
ComposeEditorWrapper component in $lib/components/compose/. Update
ContainerComposePanel to delegate to it via an onSave callback.

Co-authored-by: Michael Kaltner <mkaltner@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…ckend in the go_modules group across 1 directory (getarcaneapp#2098)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

⚡️ Feature: Add the ability to modify a compose file from the container view

6 participants