kanna-code 0.26.4 → 0.26.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <link rel="icon" type="image/png" href="/favicon.png" />
7
7
  <title>Kanna</title>
8
- <script type="module" crossorigin src="/assets/index-DDI3pFzk.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-BaEB4jie.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-C5RBxQW4.css">
10
10
  </head>
11
11
  <body>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kanna-code",
3
3
  "type": "module",
4
- "version": "0.26.4",
4
+ "version": "0.26.5",
5
5
  "description": "A beautiful web UI for Claude Code",
6
6
  "license": "MIT",
7
7
  "keywords": [
@@ -148,6 +148,52 @@ describe("DiffStore", () => {
148
148
  expect((await run(["git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], repoRoot)).trim()).toBe("origin/feature/publish-me")
149
149
  })
150
150
 
151
+ test("commit_and_push degrades to a local commit when origin is missing", async () => {
152
+ const repoRoot = await createRepo()
153
+ tempDirs.push(repoRoot)
154
+ await writeFile(path.join(repoRoot, "app.txt"), "base\n", "utf8")
155
+ await run(["git", "add", "."], repoRoot)
156
+ await run(["git", "commit", "-m", "init"], repoRoot)
157
+ await writeFile(path.join(repoRoot, "app.txt"), "changed\n", "utf8")
158
+
159
+ const store = new DiffStore(repoRoot)
160
+ await store.initialize()
161
+ await store.refreshSnapshot("project-1", repoRoot)
162
+
163
+ const result = await store.commitFiles({
164
+ projectId: "project-1",
165
+ projectPath: repoRoot,
166
+ paths: ["app.txt"],
167
+ summary: "Local only",
168
+ mode: "commit_and_push",
169
+ })
170
+
171
+ expect(result).toMatchObject({
172
+ ok: true,
173
+ mode: "commit_and_push",
174
+ pushed: false,
175
+ })
176
+ expect((await run(["git", "log", "-1", "--pretty=%s"], repoRoot)).trim()).toBe("Local only")
177
+ })
178
+
179
+ test("refreshSnapshot reports origin presence before the first commit", async () => {
180
+ const repoRoot = await createRepo()
181
+ tempDirs.push(repoRoot)
182
+ await run(["git", "remote", "add", "origin", "https://github.com/jakemor/test224.git"], repoRoot)
183
+ await writeFile(path.join(repoRoot, "poem.md"), "rose\n", "utf8")
184
+
185
+ const store = new DiffStore(repoRoot)
186
+ await store.initialize()
187
+ await store.refreshSnapshot("project-1", repoRoot)
188
+
189
+ expect(store.getProjectSnapshot("project-1")).toMatchObject({
190
+ status: "ready",
191
+ branchName: "main",
192
+ hasOriginRemote: true,
193
+ originRepoSlug: "jakemor/test224",
194
+ })
195
+ })
196
+
151
197
  test("detects renamed files", async () => {
152
198
  const repoRoot = await createRepo()
153
199
  tempDirs.push(repoRoot)
@@ -37,6 +37,7 @@ function createEmptyState(): StoredChatDiffState {
37
37
  status: "unknown",
38
38
  branchName: undefined,
39
39
  defaultBranchName: undefined,
40
+ hasOriginRemote: undefined,
40
41
  originRepoSlug: undefined,
41
42
  hasUpstream: undefined,
42
43
  aheadCount: undefined,
@@ -50,6 +51,7 @@ function createEmptyState(): StoredChatDiffState {
50
51
  function branchMetadataEqual(left: BranchMetadata, right: BranchMetadata) {
51
52
  return left.branchName === right.branchName
52
53
  && left.defaultBranchName === right.defaultBranchName
54
+ && left.hasOriginRemote === right.hasOriginRemote
53
55
  && left.originRepoSlug === right.originRepoSlug
54
56
  && left.hasUpstream === right.hasUpstream
55
57
  }
@@ -196,6 +198,9 @@ function createPushFailure(mode: DiffCommitMode, detail: string, snapshotChanged
196
198
  if (normalized.includes("non-fast-forward") || normalized.includes("fetch first")) {
197
199
  title = "Branch is not up to date"
198
200
  message = "Your branch is behind its remote. Pull or rebase, then try pushing again."
201
+ } else if (normalized.includes("does not appear to be a git repository")) {
202
+ title = "No origin remote configured"
203
+ message = "This repository does not have an origin remote configured."
199
204
  } else if (normalized.includes("has no upstream branch") || normalized.includes("set-upstream")) {
200
205
  title = "No upstream branch configured"
201
206
  message = "This branch does not have an upstream remote branch configured yet."
@@ -1317,6 +1322,7 @@ export class DiffStore {
1317
1322
  status: state.status,
1318
1323
  branchName: state.branchName,
1319
1324
  defaultBranchName: state.defaultBranchName,
1325
+ hasOriginRemote: state.hasOriginRemote,
1320
1326
  originRepoSlug: state.originRepoSlug,
1321
1327
  hasUpstream: state.hasUpstream,
1322
1328
  aheadCount: state.aheadCount,
@@ -1339,6 +1345,7 @@ export class DiffStore {
1339
1345
  status: "no_repo",
1340
1346
  branchName: undefined,
1341
1347
  defaultBranchName: undefined,
1348
+ hasOriginRemote: undefined,
1342
1349
  originRepoSlug: undefined,
1343
1350
  hasUpstream: undefined,
1344
1351
  aheadCount: undefined,
@@ -1356,6 +1363,7 @@ export class DiffStore {
1356
1363
  const branchName = await getBranchName(repo.repoRoot)
1357
1364
  const defaultBranchName = await resolveDefaultBranchName(repo.repoRoot)
1358
1365
  const originRemoteUrl = await getOriginRemoteUrl(repo.repoRoot)
1366
+ const hasOriginRemote = originRemoteUrl !== null
1359
1367
  const originRepoSlug = extractGitHubRepoSlug(originRemoteUrl) ?? undefined
1360
1368
  const hasUpstream = await hasUpstreamBranch(repo.repoRoot)
1361
1369
  const { aheadCount, behindCount } = hasUpstream
@@ -1373,6 +1381,7 @@ export class DiffStore {
1373
1381
  status: "ready",
1374
1382
  branchName,
1375
1383
  defaultBranchName,
1384
+ hasOriginRemote,
1376
1385
  originRepoSlug,
1377
1386
  hasUpstream,
1378
1387
  aheadCount,
@@ -1808,7 +1817,11 @@ export class DiffStore {
1808
1817
  throw new Error("Project is not in a git repository")
1809
1818
  }
1810
1819
 
1811
- const hasUpstream = await hasUpstreamBranch(repo.repoRoot)
1820
+ const [hasUpstream, originRemoteUrl] = await Promise.all([
1821
+ hasUpstreamBranch(repo.repoRoot),
1822
+ getOriginRemoteUrl(repo.repoRoot),
1823
+ ])
1824
+ const hasOriginRemote = originRemoteUrl !== null
1812
1825
  if (args.action === "publish") {
1813
1826
  const publishResult = await runGit(["push", "-u", "origin", "HEAD"], repo.repoRoot)
1814
1827
  if (publishResult.exitCode !== 0) {
@@ -2002,7 +2015,11 @@ export class DiffStore {
2002
2015
  if (!repo) {
2003
2016
  throw new Error("Project is not in a git repository")
2004
2017
  }
2005
- const hasUpstream = await hasUpstreamBranch(repo.repoRoot)
2018
+ const [hasUpstream, originRemoteUrl] = await Promise.all([
2019
+ hasUpstreamBranch(repo.repoRoot),
2020
+ getOriginRemoteUrl(repo.repoRoot),
2021
+ ])
2022
+ const hasOriginRemote = originRemoteUrl !== null
2006
2023
 
2007
2024
  const currentDirtyPaths = new Set((await listDirtyPaths(repo.repoRoot)).map((entry) => entry.path))
2008
2025
  const missingPaths = normalizedPaths.filter((relativePath) => !currentDirtyPaths.has(relativePath))
@@ -2039,6 +2056,16 @@ export class DiffStore {
2039
2056
  } satisfies DiffCommitResult
2040
2057
  }
2041
2058
 
2059
+ if (!hasUpstream && !hasOriginRemote) {
2060
+ return {
2061
+ ok: true,
2062
+ mode: args.mode,
2063
+ branchName,
2064
+ pushed: false,
2065
+ snapshotChanged,
2066
+ } satisfies DiffCommitResult
2067
+ }
2068
+
2042
2069
  const pushResult = hasUpstream
2043
2070
  ? await runGit(["push"], repo.repoRoot)
2044
2071
  : await runGit(["push", "-u", "origin", "HEAD"], repo.repoRoot)
@@ -57,7 +57,7 @@ export function createWsRouter({
57
57
  }: CreateWsRouterArgs) {
58
58
  const sockets = new Set<ServerWebSocket<ClientState>>()
59
59
  const resolvedDiffStore = diffStore ?? {
60
- getProjectSnapshot: () => ({ status: "unknown", branchName: undefined, defaultBranchName: undefined, originRepoSlug: undefined, hasUpstream: undefined, aheadCount: undefined, behindCount: undefined, lastFetchedAt: undefined, files: [] as const, branchHistory: { entries: [] as const } }),
60
+ getProjectSnapshot: () => ({ status: "unknown", branchName: undefined, defaultBranchName: undefined, hasOriginRemote: undefined, originRepoSlug: undefined, hasUpstream: undefined, aheadCount: undefined, behindCount: undefined, lastFetchedAt: undefined, files: [] as const, branchHistory: { entries: [] as const } }),
61
61
  refreshSnapshot: async () => false,
62
62
  initializeGit: async () => ({ ok: true, branchName: undefined, snapshotChanged: false }),
63
63
  getGitHubPublishInfo: async () => ({ ghInstalled: false, authenticated: false, activeAccountLogin: undefined, owners: [], suggestedRepoName: "my-repo" }),
@@ -544,6 +544,7 @@ export interface GitHubRepoAvailabilityResult {
544
544
  export interface BranchMetadata {
545
545
  branchName?: string
546
546
  defaultBranchName?: string
547
+ hasOriginRemote?: boolean
547
548
  originRepoSlug?: string
548
549
  hasUpstream?: boolean
549
550
  }