From bcff6ea2516670766691f5835ab2ff76c976bf34 Mon Sep 17 00:00:00 2001 From: Yian Shang Date: Thu, 23 Jul 2026 16:15:55 -0700 Subject: [PATCH] fix(ui): show git actions for feature-branch sub-namespaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The git action button gated on the structural gitShape (`=== 'branch'`), but a sub-namespace inside a feature branch inherits github_repo_path + git_branch with no parent_namespace of its own, so detectShape() returns 'flat'. Such sub-namespaces were editable (isGitManaged is false) yet fell through to the not-git "Connect to Git" button instead of Sync to Git / Create PR — the same `flat`-shape trap the read-only verdict fix (#2348) addressed, in the button logic it missed. Gate editableBranch on `!isGitManaged && (gitShape === 'branch' || isFeatureBranch)` so a feature branch and its sub-namespaces get the edit->ship controls consistently. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/app/components/NamespaceHeader.jsx | 8 +++++++- .../__tests__/NamespaceHeader.test.jsx | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/datajunction-ui/src/app/components/NamespaceHeader.jsx b/datajunction-ui/src/app/components/NamespaceHeader.jsx index b7503ec01..19856611f 100644 --- a/datajunction-ui/src/app/components/NamespaceHeader.jsx +++ b/datajunction-ui/src/app/components/NamespaceHeader.jsx @@ -1066,7 +1066,13 @@ export default function NamespaceHeader({ (() => { const repoBranch = gitConfig?.git_branch || gitConfig?.default_branch; - const editableBranch = gitShape === 'branch' && !isGitManaged; + // Editable = the branch root (shape `branch`) OR a sub-namespace + // of a feature branch (resolves as `flat`, no parent_namespace of + // its own, so `gitShape` alone misses it — same trap as the + // read-only verdict). Without isFeatureBranch these sub-namespaces + // fall through to the not-git "Connect to Git" button. + const editableBranch = + !isGitManaged && (gitShape === 'branch' || isFeatureBranch); const gitUrl = viewInGitUrl(); // Read-only: fork to propose a change; everything else in the menu. diff --git a/datajunction-ui/src/app/components/__tests__/NamespaceHeader.test.jsx b/datajunction-ui/src/app/components/__tests__/NamespaceHeader.test.jsx index 7d740a1bb..65a20d281 100644 --- a/datajunction-ui/src/app/components/__tests__/NamespaceHeader.test.jsx +++ b/datajunction-ui/src/app/components/__tests__/NamespaceHeader.test.jsx @@ -795,6 +795,24 @@ describe('', () => { }); }, ); + + // Regression: same `flat`-shape trap on the git action button. A feature + // branch sub-namespace is editable, so it must show the "Sync to Git" + // control — not the not-git "Connect to Git" button. + it('shows Sync to Git (not Connect to Git) for a feature branch sub-namespace', async () => { + renderReadOnlyVerdict({ + namespace: 'test.feature.metrics', + configByNamespace: { + 'test.feature.metrics': branchCfg('feature', 'test.feature.metrics'), + 'test.feature': branchCfg('feature', 'test.feature'), + test: GIT_ROOT, + }, + }); + await waitFor(() => + expect(screen.getByText('Sync to Git')).toBeInTheDocument(), + ); + expect(screen.queryByText('Connect to Git')).not.toBeInTheDocument(); + }); }); it('should open Create Branch modal when button is clicked', async () => {