Skip to content

Add track/stop commands and consistent local/remote symbols#3

Merged
christse merged 9 commits intomainfrom
feature/track-stop-symbols
Feb 5, 2026
Merged

Add track/stop commands and consistent local/remote symbols#3
christse merged 9 commits intomainfrom
feature/track-stop-symbols

Conversation

@christse
Copy link
Copy Markdown
Contributor

@christse christse commented Feb 4, 2026

New features:

  • boxel track - Monitor local file changes with auto-checkpointing
  • boxel stop - Stop all running watch and track processes
  • boxel history -m "message" - Create checkpoint with custom message

Symbol system for teaching local vs remote:

  • ⇆ (horizontal) for local operations (track command, LOCAL checkpoints)
  • ⇅ (vertical) for remote operations (watch command, SERVER checkpoints)

Documentation:

  • Comprehensive README rewrite with architecture diagrams
  • Updated CLAUDE.md with new commands and symbols
  • Track vs Watch comparison table

New features:
- `boxel track` - Monitor local file changes with auto-checkpointing
- `boxel stop` - Stop all running watch and track processes
- `boxel history -m "message"` - Create checkpoint with custom message

Symbol system for teaching local vs remote:
- ⇆ (horizontal) for local operations (track command, LOCAL checkpoints)
- ⇅ (vertical) for remote operations (watch command, SERVER checkpoints)

Documentation:
- Comprehensive README rewrite with architecture diagrams
- Updated CLAUDE.md with new commands and symbols
- Track vs Watch comparison table

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds new commands for local file tracking and process management, introduces a consistent symbol system to distinguish local vs remote operations, and significantly expands documentation. However, there are critical implementation gaps - the extensively documented profile management command is missing its implementation file.

Changes:

  • New track command for monitoring local file changes with automatic checkpointing
  • New stop command to terminate all running watch/track processes
  • Symbol system using ⇆ (local operations) and ⇅ (remote operations) for visual consistency
  • History command enhanced with -m flag for manual checkpoints
  • Comprehensive README rewrite with architecture diagrams and workflows
  • Extensive CLAUDE.md updates for AI-assisted development

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/index.ts Adds imports and command registrations for track, stop, and profile commands; updates help text
src/commands/track.ts New file implementing local file watching with debounced checkpoint creation
src/commands/stop.ts New file for stopping watch/track processes via process management
src/commands/watch.ts Updates display symbols from 👁 to ⇅ for consistency
src/commands/history.ts Adds manual checkpoint creation with -m flag; updates symbols from ↑/↓ to ⇆/⇅
README.md Major rewrite with architecture diagrams, workflow explanations, and comprehensive command reference
.claude/CLAUDE.md Updates with new commands, symbols, onboarding flow, and Boxel URL handling guidance

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/index.ts
import { shareCommand } from './commands/share.js';
import { gatherCommand } from './commands/gather.js';
import { realmsCommand } from './commands/realms.js';
import { profileCommand } from './commands/profile.js';
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The profile command is imported and registered but the implementation file src/commands/profile.ts is missing. This will cause a runtime error when the CLI is executed. The command needs to be either implemented or removed from the imports and command registration.

Suggested change
import { profileCommand } from './commands/profile.js';

Copilot uses AI. Check for mistakes.
Comment thread src/index.ts
Comment on lines +323 to +333
program
.command('profile')
.description('Manage saved profiles for different users/environments')
.argument('[subcommand]', 'list | add | switch | remove | migrate')
.argument('[arg]', 'Profile ID (for switch/remove)')
.option('-u, --user <matrixId>', 'Matrix user ID (e.g., @user:boxel.ai)')
.option('-p, --password <password>', 'Password (for add command)')
.option('-n, --name <displayName>', 'Display name (for add command)')
.action(async (subcommand?: string, arg?: string, options?: { user?: string; password?: string; name?: string }) => {
await profileCommand(subcommand, arg, options);
});
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The profile command is registered but the implementation file src/commands/profile.ts is missing. This command registration should be removed or the implementation should be added to prevent runtime errors.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like a good recommendation

Comment thread src/commands/track.ts Outdated

// Skip internal files
if (filename.startsWith('.boxel-') || filename.includes('.git')) return;
if (filename.startsWith('.') && !filename.startsWith('.realm.json')) return;
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for filtering files starting with '.realm.json' is incorrect. The condition !filename.startsWith('.realm.json') will skip '.realm.json' files, which contradicts the intention shown in lines 52 and 143 where '.realm.json' should be included. This should be && filename !== '.realm.json' to match the pattern used elsewhere in the code.

Suggested change
if (filename.startsWith('.') && !filename.startsWith('.realm.json')) return;
if (filename.startsWith('.') && filename !== '.realm.json') return;

Copilot uses AI. Check for mistakes.
Comment thread src/commands/stop.ts Outdated
Comment on lines +12 to +17
const stopped: StoppedProcess[] = [];

try {
// Find boxel watch and track processes
const result = execSync(
`ps aux | grep -E 'tsx.*src/index.ts (watch|track)' | grep -v grep`,
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stop command uses Unix-specific shell commands ('ps aux', 'grep') which will fail on Windows. This command should either detect the platform and use appropriate commands (e.g., tasklist/taskkill on Windows) or document that it only works on Unix-like systems. Additionally, the regex pattern assumes 'tsx' is in the command, but when installed globally with 'npm install -g', the process name will be 'boxel' or 'node', not 'tsx'.

Suggested change
const stopped: StoppedProcess[] = [];
try {
// Find boxel watch and track processes
const result = execSync(
`ps aux | grep -E 'tsx.*src/index.ts (watch|track)' | grep -v grep`,
// Currently, process discovery relies on Unix-specific tools (ps/grep).
// Avoid running this on Windows, where these commands are unavailable.
if (process.platform === 'win32') {
console.log(' The stop command is only supported on Unix-like systems at this time.');
return;
}
const stopped: StoppedProcess[] = [];
try {
// Find boxel watch and track processes.
// Match commands that include both "boxel" and "watch"/"track"
// (e.g., "boxel watch ..." or "node ...boxel... track ...").
const result = execSync(
`ps aux | grep -E 'boxel.*(watch|track)|(watch|track).*boxel' | grep -v grep`,

Copilot uses AI. Check for mistakes.
Comment thread src/index.ts
Comment on lines +384 to +388
boxel profile Show current profile
boxel profile list List all saved profiles
boxel profile add Add a new profile (interactive)
boxel profile switch <id> Switch to a different profile
boxel profile migrate Import credentials from .env
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The help text documents extensive profile command usage (lines 384-388), but the profile command implementation is missing. This will confuse users who try to follow the documented examples. These examples should either be removed or the profile command implementation should be added.

Copilot uses AI. Check for mistakes.
Comment thread README.md Outdated
Comment on lines +132 to +133
boxel profile add # Interactive setup
boxel profile add -u @user:boxel.ai -p "pass" -n "Prod" # Non-interactive
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example boxel profile add -u @user:boxel.ai -p "pass" -n "Prod" encourages passing real passwords directly on the command line, which exposes credentials to other local users via process listings, shell history, and orchestration logs. An attacker with local access can capture the password by inspecting running processes or historic commands and then log into the Boxel account. Prefer documenting the interactive profile add flow or environment-based configuration for real usage, and clearly warn against supplying real passwords via the -p flag in CLI examples.

Suggested change
boxel profile add # Interactive setup
boxel profile add -u @user:boxel.ai -p "pass" -n "Prod" # Non-interactive
boxel profile add # Interactive setup (recommended for real accounts)
BOXEL_PASSWORD="your-password" boxel profile add -u @user:boxel.ai -p "$BOXEL_PASSWORD" -n "Prod" # Non-interactive; avoid real passwords in shell history

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like a good recommendation

Comment thread .claude/CLAUDE.md Outdated
Comment on lines +74 to +77
npm run dev -- profile add -u @username:boxel.ai -p "password" -n "My Prod Account"

**Staging:**
```env
MATRIX_URL=https://matrix-staging.stack.cards
MATRIX_USERNAME=<ask for username>
MATRIX_PASSWORD=<ask for password>
REALM_SERVER_URL=https://realms-staging.stack.cards/
# Staging
npm run dev -- profile add -u @username:stack.cards -p "password" -n "My Staging Account"
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These non-interactive examples (npm run dev -- profile add -u @username:boxel.ai -p "password" -n ...) promote passing real passwords as CLI arguments, which can leak credentials via process lists, shell history, and CI/CD or container orchestration metadata. A local attacker (or anyone with access to process metadata/logs) could capture these passwords and reuse them to authenticate to Boxel. Recommend emphasizing the interactive profile add flow for humans and more secure secret injection mechanisms (env vars, secret stores) for automation, and explicitly warning against using -p with real passwords in scripts.

Copilot uses AI. Check for mistakes.
Comment thread .claude/CLAUDE.md Outdated
Comment on lines +206 to +207
boxel profile add # Interactive wizard to add profile
boxel profile add -u @user:boxel.ai -p pass -n "Name" # Non-interactive
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The boxel profile add -u @user:boxel.ai -p pass -n "Name" example teaches users to provide passwords on the command line, which is insecure because arguments are visible to other local users via ps, shell history, and some logging systems. An attacker with access to the same host could harvest these credentials and log into the associated Boxel account. Update the docs to recommend the interactive profile add flow for entering passwords and reserve any non-interactive usage for safer secret mechanisms instead of -p.

Suggested change
boxel profile add # Interactive wizard to add profile
boxel profile add -u @user:boxel.ai -p pass -n "Name" # Non-interactive
boxel profile add # Interactive wizard to add profile (recommended)
# For non-interactive/CI usage, do NOT pass passwords via -p; use supported secret mechanisms per the Boxel CLI docs.

Copilot uses AI. Check for mistakes.
Comment thread src/index.ts
Comment on lines +323 to +333
program
.command('profile')
.description('Manage saved profiles for different users/environments')
.argument('[subcommand]', 'list | add | switch | remove | migrate')
.argument('[arg]', 'Profile ID (for switch/remove)')
.option('-u, --user <matrixId>', 'Matrix user ID (e.g., @user:boxel.ai)')
.option('-p, --password <password>', 'Password (for add command)')
.option('-n, --name <displayName>', 'Display name (for add command)')
.action(async (subcommand?: string, arg?: string, options?: { user?: string; password?: string; name?: string }) => {
await profileCommand(subcommand, arg, options);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like a good recommendation

- Fix track.ts file filtering: use `filename !== '.realm.json'` instead of
  `!filename.startsWith('.realm.json')`
- Add Windows compatibility check in stop.ts with helpful message
- Improve stop.ts process matching to handle both dev (tsx) and installed
  (boxel, node) modes
- Add BOXEL_PASSWORD env var support in profile.ts for secure non-interactive
  usage
- Fix touch.ts missing sync() method implementation
- Add profile manager integration to check.ts, create.ts, list.ts, skills.ts,
  status.ts (was using old env var approach)
- Update docs to use `npx boxel` as default command pattern
- Add security notes about avoiding `-p` flag for passwords in shell history
- Add Claude Code note at top of README with link to claude.ai/code
- Streamline installation section in README

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 10 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/commands/history.ts Outdated
Comment thread src/commands/track.ts Outdated
Comment thread src/index.ts Outdated
Comment thread src/index.ts
Comment thread src/commands/track.ts
Comment thread src/commands/list.ts
Comment thread src/index.ts
Comment thread src/commands/track.ts Outdated
Comment thread src/commands/stop.ts Outdated
Comment thread src/commands/profile.ts Outdated
Copy link
Copy Markdown

Copilot AI commented Feb 4, 2026

@christse I've opened a new pull request, #4, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 4 commits February 4, 2026 19:33
Code improvements from Copilot review:
- history.ts: Scan workspace for changes when creating manual checkpoint
- track.ts: Use consistent ISO timestamp format, add mutex for race conditions, add Linux fs.watch warning
- stop.ts: Use more specific grep pattern to avoid false positives
- profile.ts: Remove unused variable
- profile-manager.ts: Add REALM_SERVER_URL derivation from MATRIX_URL
- index.ts: Add password security warning, clarify env var requirements, add track/stop examples

Documentation updates:
- Add new /track skill for Claude Code
- Update CLAUDE.md with /track skill and track→sync workflow
- Update README.md to emphasize that track requires sync to push changes

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 12 comments.

Comments suppressed due to low confidence (1)

src/commands/create.ts:72

  • The realm server URL derivation in the create command (lines 62-72) is redundant with the derivation already performed in ProfileManager.getActiveCredentials(). Since baseRealmServerUrl comes from credentials which already performs URL derivation, the if (!realmServerUrl) block should rarely execute. However, there's an inconsistency: the create command only handles two patterns (matrix- and matrix.) while ProfileManager handles three patterns (matrix., matrix-staging., and matrix-). Either remove this redundant derivation and rely on ProfileManager, or make the patterns consistent across both locations.
  let realmServerUrl = baseRealmServerUrl;
  if (!realmServerUrl) {
    const matrixUrlObj = new URL(matrixUrl);
    if (matrixUrlObj.hostname.startsWith('matrix-')) {
      realmServerUrl = `${matrixUrlObj.protocol}//${matrixUrlObj.hostname.slice(7)}/`;
    } else if (matrixUrlObj.hostname.startsWith('matrix.')) {
      realmServerUrl = `${matrixUrlObj.protocol}//app.${matrixUrlObj.hostname.slice(7)}/`;
    } else {
      console.error('Could not derive realm server URL. Set REALM_SERVER_URL.');
      process.exit(1);
    }
  }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/commands/track.ts
Comment thread src/commands/track.ts
Comment thread src/commands/profile.ts
Comment thread src/commands/history.ts
Comment thread src/commands/touch.ts
Comment thread src/commands/stop.ts
Comment thread src/commands/stop.ts
Comment thread src/commands/profile.ts
Comment thread src/index.ts
Comment thread src/commands/track.ts
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/commands/profile.ts
Comment on lines +200 to +206
const username = await prompt('Username: ');

if (!username) {
console.error(`${FG_RED}Error:${RESET} Username is required.`);
process.exit(1);
}

Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The username input validation only checks if it's empty (line 202-205), but doesn't validate that it contains valid characters for a Matrix ID. Matrix usernames have specific character restrictions (typically lowercase letters, numbers, hyphens, underscores, periods).

If a user enters invalid characters (like spaces, uppercase letters, or special characters), the resulting Matrix ID @${username}:${domain} may be invalid and authentication will fail with a confusing error.

Consider adding validation for the username format:

  • Check for invalid characters
  • Suggest converting to lowercase
  • Provide helpful error messages about what characters are allowed

This would improve the user experience by catching errors early during profile creation.

Suggested change
const username = await prompt('Username: ');
if (!username) {
console.error(`${FG_RED}Error:${RESET} Username is required.`);
process.exit(1);
}
const rawUsername = await prompt('Username: ');
const username = rawUsername.trim();
if (!username) {
console.error(`${FG_RED}Error:${RESET} Username is required.`);
process.exit(1);
}
// Matrix usernames must be lowercase and may contain a–z, 0–9, '-', '_', and '.'
const usernamePattern = /^[a-z0-9._-]+$/;
if (!usernamePattern.test(username)) {
console.error(
`${FG_RED}Error:${RESET} Invalid username. Usernames must be lowercase and may only contain ` +
`letters a–z, numbers 0–9, hyphens (-), underscores (_), and periods (.).`
);
process.exit(1);
}

Copilot uses AI. Check for mistakes.
Comment thread src/commands/stop.ts
Comment on lines +25 to +28
const result = execSync(
`ps aux | grep -E '(tsx[[:space:]].*src/index\\.ts[[:space:]]+(watch|track)|[[:space:]]boxel[[:space:]]+(watch|track)|node[[:space:]].*boxel[[:space:]]+(watch|track))' | grep -v grep | grep -v '[[:space:]]stop'`,
{ encoding: 'utf-8' }
).trim();
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern for finding boxel processes could potentially match unintended processes. For example, if a user has a file or directory named "boxel" in their path, or if there's another tool with "boxel" in its name, this could incorrectly match those processes. The pattern [[:space:]]boxel[[:space:]] helps but may still match false positives.

Consider adding additional safety checks:

  1. Verify the process is actually a node/tsx process
  2. Check that the executable path contains the expected boxel CLI location
  3. Add a dry-run mode to show what would be killed before actually killing processes

This would make the command safer and more predictable.

Copilot uses AI. Check for mistakes.
Comment on lines +177 to +181
for (const line of statusOutput.split('\n')) {
if (!line) continue;

const statusCode = line.substring(0, 2);
let file = line.substring(3);
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The git status parsing uses line.substring(0, 2) and line.substring(3) to extract the status code and filename. This assumes the git status format is always consistent, but git status can have variations:

  1. If a file has spaces in its name and is quoted by git (e.g., "file name.txt"), the substring(3) will include the quotes
  2. For renamed files with ->, the parsing handles this, but the substring index could be off if git quotes the filenames

Consider using git status --porcelain=v1 (explicit version) and handling quoted filenames properly by checking if the filename starts with a quote and parsing accordingly. Git uses C-style quoting for filenames with special characters.

This is a minor edge case but could cause issues with files that have unusual names.

Copilot uses AI. Check for mistakes.
Comment thread src/commands/history.ts
Comment on lines +6 to 36
/**
* Scan workspace directory to build a changes array for manual checkpoints.
* Marks all current files as 'modified' since we're snapshotting the current state.
*/
function scanWorkspaceForChanges(workspaceDir: string): CheckpointChange[] {
const changes: CheckpointChange[] = [];

const scan = (dir: string, prefix = '') => {
if (!fs.existsSync(dir)) return;

const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
// Skip internal files
if (entry.name.startsWith('.boxel-') || entry.name === '.git') continue;
if (entry.name.startsWith('.') && entry.name !== '.realm.json') continue;

const fullPath = path.join(dir, entry.name);
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;

if (entry.isDirectory()) {
scan(fullPath, relativePath);
} else {
changes.push({ file: relativePath, status: 'modified' });
}
}
};

scan(workspaceDir);
return changes;
}

Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused function scanWorkspaceForChanges.

Suggested change
/**
* Scan workspace directory to build a changes array for manual checkpoints.
* Marks all current files as 'modified' since we're snapshotting the current state.
*/
function scanWorkspaceForChanges(workspaceDir: string): CheckpointChange[] {
const changes: CheckpointChange[] = [];
const scan = (dir: string, prefix = '') => {
if (!fs.existsSync(dir)) return;
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
// Skip internal files
if (entry.name.startsWith('.boxel-') || entry.name === '.git') continue;
if (entry.name.startsWith('.') && entry.name !== '.realm.json') continue;
const fullPath = path.join(dir, entry.name);
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
if (entry.isDirectory()) {
scan(fullPath, relativePath);
} else {
changes.push({ file: relativePath, status: 'modified' });
}
}
};
scan(workspaceDir);
return changes;
}

Copilot uses AI. Check for mistakes.
@christse christse merged commit f80821c into main Feb 5, 2026
6 checks passed
@christse christse deleted the feature/track-stop-symbols branch February 13, 2026 17:30
christse pushed a commit that referenced this pull request Apr 20, 2026
Six fixes in one commit, addressing every Copilot comment except #7
(dead code, deferred to a later cleanup pass).

#4 — manifest shape mismatch (critical):
  push.ts was still reading/writing the old manifest format
  (files[path] = hashString) while pull.ts and sync.ts use the new
  {localHash, remoteMtime} shape. push now uses the new shape,
  migrates old manifests on read (mirrors sync.ts detector), and
  refreshes remoteMtime via getRemoteMtimes() after a successful
  upload so the next pull/sync sees a consistent picture.

#3 — partial-success manifest (important):
  In --batch mode we were marking every instance as synced when
  result.uploaded > 0, even if some failed. Now we derive failed
  paths from result.errors and only add successes to the manifest.
  Failed uploads stay out and get retried on the next run.

#6 — ATOMIC_SOURCE_EXTENSIONS gap (important):
  EXTENSION_MAP was missing .tsx/.jsx/.cjs/.scss/.less/.sass, so
  those extensions got application/octet-stream and were routed
  through individual POST instead of /_atomic despite being listed
  as atomic-source compatible. Now mapped to
  application/typescript/javascript and text/x-* respectively, so
  isTextFile() returns true and they take the atomic path.

#2 — CLI --version out of sync:
  program.version() was hardcoded to '1.0.0' while package.json is
  at 1.0.1. Now reads from package.json via createRequire so
  boxel --version stays accurate on every release.

#1 — --batch-size NaN guard:
  parseInt on bad input (e.g. "abc") returned NaN, which bypasses
  `?? 10` and flows into the uploader as NaN. Now uses a
  parsePositiveInt parser that throws InvalidArgumentError with
  a friendly message on non-positive-integer input.

#5 — invalid-JSON test expectation:
  The test expected data.type='file' but the code (correctly) emits
  'source' because /_atomic only accepts 'card' and 'source'
  resource types. Test updated to match the correct contract.

All 164 tests pass (was 163/164 before this commit).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
christse pushed a commit that referenced this pull request Apr 20, 2026
Six fixes in one commit, addressing every Copilot comment except #7
(dead code, deferred to a later cleanup pass).

#4 — manifest shape mismatch (critical):
  push.ts was still reading/writing the old manifest format
  (files[path] = hashString) while pull.ts and sync.ts use the new
  {localHash, remoteMtime} shape. push now uses the new shape,
  migrates old manifests on read (mirrors sync.ts detector), and
  refreshes remoteMtime via getRemoteMtimes() after a successful
  upload so the next pull/sync sees a consistent picture.

#3 — partial-success manifest (important):
  In --batch mode we were marking every instance as synced when
  result.uploaded > 0, even if some failed. Now we derive failed
  paths from result.errors and only add successes to the manifest.
  Failed uploads stay out and get retried on the next run.

#6 — ATOMIC_SOURCE_EXTENSIONS gap (important):
  EXTENSION_MAP was missing .tsx/.jsx/.cjs/.scss/.less/.sass, so
  those extensions got application/octet-stream and were routed
  through individual POST instead of /_atomic despite being listed
  as atomic-source compatible. Now mapped to
  application/typescript/javascript and text/x-* respectively, so
  isTextFile() returns true and they take the atomic path.

#2 — CLI --version out of sync:
  program.version() was hardcoded to '1.0.0' while package.json is
  at 1.0.1. Now reads from package.json via createRequire so
  boxel --version stays accurate on every release.

#1 — --batch-size NaN guard:
  parseInt on bad input (e.g. "abc") returned NaN, which bypasses
  `?? 10` and flows into the uploader as NaN. Now uses a
  parsePositiveInt parser that throws InvalidArgumentError with
  a friendly message on non-positive-integer input.

#5 — invalid-JSON test expectation:
  The test expected data.type='file' but the code (correctly) emits
  'source' because /_atomic only accepts 'card' and 'source'
  resource types. Test updated to match the correct contract.

All 164 tests pass (was 163/164 before this commit).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
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.

4 participants