amalgm 0.1.188 → 0.1.189

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amalgm",
3
- "version": "0.1.188",
3
+ "version": "0.1.189",
4
4
  "description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -341,6 +341,37 @@ test('the live service hears a commit through the git-dir doorbell', async () =>
341
341
  assert.ok(repoCardServiceStats().doorbellRings >= 1);
342
342
  });
343
343
 
344
+ test('the live service hears nested branch creation and deletion', async () => {
345
+ const repo = await makeRepo('projects/belled-refs');
346
+ const projects = path.join(tempRoot, 'projects');
347
+ upsertWorkspace({ path: projects, name: 'Projects', source: 'computer' });
348
+ startRepoCardService();
349
+ await refreshRepoCards();
350
+ const branch = 'storm/nested-short';
351
+ const before = findCard(listRepoCards(), repo);
352
+
353
+ await run(repo, 'branch', branch);
354
+ let deadline = Date.now() + 5_000;
355
+ let created = findCard(listRepoCards(), repo);
356
+ while (!created.refs.some((ref) => ref.name === branch) && Date.now() < deadline) {
357
+ await new Promise((resolve) => setTimeout(resolve, 25));
358
+ created = findCard(listRepoCards(), repo);
359
+ }
360
+ assert.ok(created.refs.some((ref) => ref.name === branch));
361
+
362
+ await run(repo, 'branch', '-D', branch);
363
+ deadline = Date.now() + 5_000;
364
+ let deleted = findCard(listRepoCards(), repo);
365
+ while (deleted.refs.some((ref) => ref.name === branch) && Date.now() < deadline) {
366
+ await new Promise((resolve) => setTimeout(resolve, 25));
367
+ deleted = findCard(listRepoCards(), repo);
368
+ }
369
+ stopRepoCardService();
370
+
371
+ assert.equal(deleted.refs.some((ref) => ref.name === branch), false);
372
+ assert.equal(deleted.generation, before.generation + 2);
373
+ });
374
+
344
375
  test('the live service hears an uncommitted nested edit without a polling pass', async () => {
345
376
  const repo = await makeRepo('projects/live-dirty');
346
377
  const projects = path.join(tempRoot, 'projects');
@@ -413,24 +413,49 @@ function ringDoorbell(replicaId) {
413
413
  bell.timer = setTimeout(() => {
414
414
  bell.timer = null;
415
415
  refreshRepoCard(replicaId)
416
- .then(() => require('./checkpoint').publishCheckpoints(replicaId))
416
+ .then((card) => {
417
+ if (card) rebuildDoorbell(replicaId, card);
418
+ return require('./checkpoint').publishCheckpoints(replicaId);
419
+ })
417
420
  .catch((error) => console.warn('[RepoCards] doorbell refresh failed:', error.message));
418
421
  }, DOORBELL_QUIET_MS);
419
422
  bell.timer.unref?.();
420
423
  }
421
424
 
425
+ function nestedDirectories(root) {
426
+ const directories = [];
427
+ const pending = [root];
428
+ while (pending.length > 0) {
429
+ const current = pending.pop();
430
+ let entries;
431
+ try {
432
+ entries = fs.readdirSync(current, { withFileTypes: true });
433
+ } catch {
434
+ continue;
435
+ }
436
+ directories.push(current);
437
+ for (const entry of entries) {
438
+ if (entry.isDirectory()) pending.push(path.join(current, entry.name));
439
+ }
440
+ }
441
+ return directories;
442
+ }
443
+
422
444
  function watchGitDir(card) {
423
445
  const watchers = [];
424
446
  const ring = () => ringDoorbell(card.replica);
425
447
  // The git dir itself carries HEAD, index, and operation markers; logs/
426
448
  // carries the reflog, which moves on every commit, checkout, and reset.
427
449
  const common = card.commonGitDir || card.gitDir;
428
- for (const dir of [
450
+ const direct = [
429
451
  card.gitDir,
430
452
  path.join(card.gitDir, 'logs'),
431
- path.join(common, 'refs', 'heads'),
432
- path.join(common, 'logs', 'refs', 'heads'),
433
- ]) {
453
+ ];
454
+ const refDirectories = [
455
+ ...nestedDirectories(path.join(common, 'refs', 'heads')),
456
+ ...nestedDirectories(path.join(common, 'logs', 'refs', 'heads')),
457
+ ];
458
+ for (const dir of new Set([...direct, ...refDirectories])) {
434
459
  try {
435
460
  const watcher = fs.watch(dir, ring);
436
461
  watcher.on('error', () => watcher.close());
@@ -455,6 +480,14 @@ function watchGitDir(card) {
455
480
  return watchers;
456
481
  }
457
482
 
483
+ function rebuildDoorbell(replicaId, card) {
484
+ const bell = service.doorbells.get(replicaId);
485
+ if (!bell) return;
486
+ const previous = bell.watchers;
487
+ bell.watchers = watchGitDir(card);
488
+ for (const watcher of previous) watcher.close();
489
+ }
490
+
458
491
  function dropDoorbell(replicaId) {
459
492
  const bell = service.doorbells.get(replicaId);
460
493
  if (!bell) return;