Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions .claude/commands/commit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Commit Changes

Complete workflow: branch → commit → push → PR

## Usage

```bash
/commit [options]
```

**Options:**

- `--push` or `-p`: Push to remote after commit
- `--pr`: Create PR after push
- `--all` or `-a`: Commit all changes at once
- `<path>`: Commit only specific path (e.g., `packages/kstyled`, `packages/babel-plugin-kstyled`)

## Examples

```bash
# Full workflow: commit src changes, push, create PR
/commit packages/kstyled --pr

# Commit all and create PR
/commit --all --pr

# Just commit specific path
/commit packages/babel-plugin-kstyled
```

## Complete Workflow

### 1. Check Branch

```bash
# Check current branch
git branch --show-current
```

**If on `main`** → Create a feature branch first:

```bash
git checkout -b feat/<feature-name>
```

**If NOT on `main`** → Proceed with commits directly.

**Branch naming conventions:**

- `feat/<feature-name>` - New features
- `fix/<bug-description>` - Bug fixes
- `docs/<doc-update>` - Documentation only
- `chore/<task>` - Maintenance tasks

### 2. Pre-Commit Checks (CRITICAL)

Before staging any changes, run the following checks:

```bash
# Lint check
bun run lint

# Type check
bun run typecheck

# Run tests
bun run test
```

**IMPORTANT:** Only proceed with commit if ALL checks pass.

### 3. Check Current Status

```bash
git status
git diff --name-only
```

### 4. Stage Changes

**kstyled package:**

```bash
git add packages/kstyled/
```

**Babel plugin:**

```bash
git add packages/babel-plugin-kstyled/
```

**All changes:**

```bash
git add .
```

### 5. Review Staged Changes

```bash
git diff --cached --stat
git diff --cached --name-only
```

### 6. Create Commit

Follow Angular Conventional Commit format:

```bash
git commit -m "$(cat <<'EOF'
<type>(<scope>): <description>

<body - what changed and why>

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)"
```

**Commit Types:** | Type | Description | |------|-------------| | `feat` | New feature | | `fix` | Bug fix | | `docs` | Documentation only | | `refactor` | Code refactoring | | `chore` | Maintenance tasks | | `test` | Adding/updating tests | | `perf` | Performance improvement | | `style` | Code style (formatting, semicolons, etc.) |

**Scope Examples:**

- `css` - css`` tagged template helper
- `styled` - styled component system
- `theme` - Theme provider
- `babel` - Babel plugin changes
- `types` - TypeScript type definitions

### 7. Push to Remote

```bash
git push -u origin <branch-name>
```

### 8. Create Pull Request

```bash
gh pr create --title "<type>(<scope>): <description>" --body "$(cat <<'EOF'
## Summary

<1-3 bullet points describing changes>

## Changes

- Change 1
- Change 2

## Test plan

- [ ] `bun run lint` passes
- [ ] `bun run typecheck` passes
- [ ] `bun run test` passes

🤖 Generated with [Claude Code](https://claude.ai/code)
EOF
)"
```

---

## Important Notes

- **ALWAYS** run pre-commit checks before committing
- **ALWAYS** include `Co-Authored-By` footer for Claude-assisted commits
- Use `bun` exclusively for all package management
111 changes: 111 additions & 0 deletions .claude/commands/review-pr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Review PR Comments

Review and address PR review comments for this repository.

## Arguments

- `$ARGUMENTS` - PR number (e.g., `123`) or PR URL

## Project-Specific Build Commands

Based on changed files, run these checks BEFORE committing:

| Path | Commands |
| --- | --- |
| `packages/kstyled/` | `bun run lint && bun run typecheck && bun run test` |
| `packages/babel-plugin-kstyled/` | `bun run lint && bun run typecheck && bun run test` |

## Workflow

### Step 1: Get PR Information

```bash
# Get PR review comments
gh api repos/{owner}/{repo}/pulls/{pr_number}/comments

# Get PR reviews (approve, request changes, etc.)
gh api repos/{owner}/{repo}/pulls/{pr_number}/reviews

# Get changed files
gh pr view {pr_number} --json files
```

### Step 2: Analyze Each Comment

For each review comment:

1. `path` - File path
2. `line` or `original_line` - Line number
3. `body` - Review content
4. `diff_hunk` - Code context
5. Determine if code change is needed

### Step 3: Apply Fixes

1. Read the target file
2. Apply changes per reviewer feedback
3. Track changes with TodoWrite
4. Run project-specific checks

### Step 4: Run Checks Before Commit

```bash
bun run lint
bun run typecheck
bun run test
```

### Step 5: Commit Changes

```bash
git add <changed-files>
git commit -m "$(cat <<'EOF'
fix: address PR review comments

- <summary of change 1>
- <summary of change 2>

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)"
```

### Step 6: Reply to Comments

Reply to each addressed comment:

```bash
gh api repos/{owner}/{repo}/pulls/{pr_number}/comments/{comment_id}/replies \
-X POST -f body="Fixed in abc1234.

**Changes:**
- Description of what was changed"
```

## Reply Format Rules (CRITICAL)

When replying to PR comments:

### Commit Hash Formatting

**NEVER wrap commit hashes in backticks or code blocks.** GitHub only auto-links plain text commit hashes.

| Format | Example | Result |
| ---------- | ----------------------- | ------------------------ |
| CORRECT | Fixed in f3b5fec. | Clickable link to commit |
| WRONG | `Fixed in \`f3b5fec\`.` | Plain text, no link |

## Result Report

After addressing all comments, report:

- List of modified files
- Summary of changes per file
- Commit hash
- Any comments not addressed and why

## Notes

- If a comment is a question or praise, no code change needed
- If reviewer intent is unclear, ask for clarification
- Use `bun` exclusively for all commands
38 changes: 38 additions & 0 deletions packages/kstyled/src/__tests__/runtime.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@
});

test('should forward attrs values to rendered component', () => {
const Base = React.forwardRef((props: any, _ref) => {

Check warning on line 240 in packages/kstyled/src/__tests__/runtime.test.tsx

View workflow job for this annotation

GitHub Actions / test

'_ref' is defined but never used
return React.createElement('Base', props);
});

Expand All @@ -262,8 +262,8 @@
});

test('should allow attrs to override base component via as', () => {
const Base = React.forwardRef((props: any, _ref) => React.createElement('Base', props));

Check warning on line 265 in packages/kstyled/src/__tests__/runtime.test.tsx

View workflow job for this annotation

GitHub Actions / test

'_ref' is defined but never used
const Override = React.forwardRef((props: any, _ref) => React.createElement('Override', props));

Check warning on line 266 in packages/kstyled/src/__tests__/runtime.test.tsx

View workflow job for this annotation

GitHub Actions / test

'_ref' is defined but never used

const StyledComp = styled(Base).__withStyles({
attrs: () => ({
Expand Down Expand Up @@ -426,6 +426,44 @@
expect(patch.marginTop).toBe(32);
expect(patch.backgroundColor).toBe('#007AFF');
});

test('should not crash when getDynamicPatch throws (theme-dependent css``)', () => {
// Simulates compiled css`` with theme interpolation: css`color: ${({theme}) => theme.text.primary}`
// When __withStyles calls getDynamicPatch({}), theme is {} so theme.text is undefined
const getDynamicPatch = (props: any) => ({
color: props.theme.text.primary, // This throws: Cannot read properties of undefined
});

const metadata: any = {
getDynamicPatch,
};

// Should not throw - gracefully handles the error
expect(() => css.__withStyles(metadata)).not.toThrow();

// Should return empty styles (no dynamic patch applied)
const result = css.__withStyles(metadata);
expect(result).toEqual([]);
});

test('should not crash when JSON.stringify encounters circular references', () => {
const circular: any = { color: 'red' };
circular.self = circular;

const getDynamicPatch = () => circular;

const metadata: any = {
getDynamicPatch,
};

// Should not throw
expect(() => css.__withStyles(metadata)).not.toThrow();

// Should still push the patch even without caching
const result = css.__withStyles(metadata);
expect(result).toBe(circular);
expect(metadata._cachedDynamic).toBeUndefined();
});
});

describe('Hybrid Styles (Static + Dynamic)', () => {
Expand Down
47 changes: 31 additions & 16 deletions packages/kstyled/src/css.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,24 +205,39 @@ export const css: CssFactory = Object.assign(
// The dynamic values are captured in the closure when Babel plugin creates this function
// So SCREEN_WIDTH and other module-level constants work correctly
if (getDynamicPatch) {
const dynamicPatch = getDynamicPatch({});
try {
const dynamicPatch = getDynamicPatch({});

if (dynamicPatch && Object.keys(dynamicPatch).length > 0) {
// Automatic memoization: Create hash from dynamic values
const hash = JSON.stringify(dynamicPatch, (_key, value) =>
value === undefined ? '__ks__undefined__' : value
);
if (dynamicPatch && Object.keys(dynamicPatch).length > 0) {
// Automatic memoization: Create hash from dynamic values
let hash: string;
try {
hash = JSON.stringify(dynamicPatch, (_key, value) =>
value === undefined ? '__ks__undefined__' : value
);
} catch {
// Fallback for non-serializable values (e.g., circular references on web)
hash = '';
}

// Check if we can reuse the cached patch
if (metadata._cachedDynamic && metadata._cachedDynamic.hash === hash) {
styles.push(metadata._cachedDynamic.patch);
} else {
// Cache miss: store new patch
metadata._cachedDynamic = {
patch: dynamicPatch,
hash: hash,
};
styles.push(dynamicPatch);
if (hash && metadata._cachedDynamic && metadata._cachedDynamic.hash === hash) {
// Check if we can reuse the cached patch
styles.push(metadata._cachedDynamic.patch);
} else {
// Cache miss or non-hashable: store new patch
if (hash) {
metadata._cachedDynamic = { patch: dynamicPatch, hash };
}
styles.push(dynamicPatch);
}
}
} catch (error) {
// getDynamicPatch({}) may fail when it contains theme-dependent
// functions (e.g., ({theme}) => theme.bg.basic) since {} has no theme.
// This is expected for css`` used inside styled components where
// theme is provided at render time, not at definition time.
if (typeof __DEV__ !== 'undefined' && __DEV__) {
console.warn('[kstyled] css.__withStyles getDynamicPatch({}) failed:', error);
}
}
}
Expand Down
Loading