Skip to content
Closed
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
197 changes: 197 additions & 0 deletions .agents/skills/implement/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
---
name: implement
description: "End-to-end implementation workflow: plan, implement, verify, commit, and open a draft PR. Use when asked to implement, fix, build, or work on something."
---

# Implement Workflow

This skill handles the full lifecycle: plan -> implement -> verify -> commit -> draft PR.

## Phase 1: Plan (requires approval)

1. **Understand the request** — Read the issue description, linked context, or user instructions.
2. **Explore the codebase** — Find relevant files, understand existing patterns, read tests.
3. **Create an implementation plan**:
- What files will be modified/created
- What the changes will do
- What edge cases to consider
- What tests to add or update
4. **Present the plan and STOP** — Wait for explicit user approval before proceeding.

Do NOT start coding until the plan is approved. If requirements are ambiguous, ask.

## Phase 2: Implement

Execute the approved plan:
- Follow existing code patterns and conventions in the repo
- Keep changes minimal and focused — don't refactor unrelated code
- Kotlin warnings are treated as errors (`allWarningsAsErrors = true`) — write clean code
- Use `org.wordpress.aztec` package conventions
- **Unit tests**: Add or update tests covering new/changed logic. Follow existing test patterns in the repo. If writing meaningful tests would require disproportionate effort (e.g., complex setup, heavy mocking of framework internals), skip but notify the developer explaining why.

## Phase 3: Verify

Run verification checks and fix any failures. Iterate until all checks pass.

### 3a: Compile

```bash
./gradlew :aztec:assembleRelease :app:assembleDebug
```

If other modules were changed, compile those too:
```bash
./gradlew assembleRelease
```

### 3b: Lint

```bash
./gradlew ktlint
```

- Fix violations — prefer real fixes over suppression
- Only suppress when it's a false positive and document why

### 3c: Unit Tests

```bash
./gradlew aztec:testRelease
```

Or run specific tests related to the changes:
```bash
./gradlew :aztec:testReleaseUnitTest --tests "org.wordpress.aztec.SpecificTest" --info
```

**Test rules:**
- NEVER weaken or remove assertions to make tests pass
- NEVER modify production code just to pass a test without permission
- Tests that pass only by not crashing are invalid — every test needs meaningful assertions
- If a test won't pass after reasonable attempts: stop and ask

### 3d: Fix Errors

If any step fails:
1. Analyze the error
2. Fix the issue
3. Re-run the failing check
4. Repeat until green

## Phase 4: Present Changes (requires approval)

Show a summary of all changes made:
- Files modified/created
- Key behavioral changes
- Test coverage

**STOP and wait for user approval** before committing.

## Phase 5: Commit and Draft PR

Only proceed after explicit approval.

### 5a: Run Final Lint

```bash
./gradlew ktlint
```

Fix any remaining issues.

### 5b: Inspect Changes

```bash
git status
git diff --stat
git diff
```

### 5c: Plan Commits

Review the changes and determine if they should be split into multiple commits:
- Independent logical units = separate commits
- Bug fix + feature = separate commits
- Formatting + logic = separate commits

For **each commit**, prepare:
1. **Commit message**: Imperative summary + brief body
2. **Files**: Paths to stage
3. **Summary**: What and why

**Commit message format** — use direct multi-line strings:
```bash
git commit -m "Imperative summary

- Detail one
- Detail two
"
```

**Rules:**
- NO co-author lines — NEVER add "Co-Authored-By" or AI attribution
- Each commit should be cohesive and buildable
- Use `git add -p` to split mixed concerns if needed

### 5d: Stage and Commit

```bash
git add <specific files>
git commit -m "message"
```

### 5e: Push and Create Draft PR

```bash
# Get the correct remote owner/repo
git remote get-url origin

# Push
git push -u origin HEAD

# Create draft PR
PAGER=cat gh pr create --draft --title "PR title" --body "$(cat <<'EOF'
### Fix
<description>

### Test
1. Step 1
2. Step 2
EOF
)"
```

**PR rules:**
- Title: short, under 70 characters
- Body: follows the repo's PR template format (### Fix, ### Test, ### Review)
- Always create as **draft**
- Return the PR URL when done

## Handling Edge Cases

### Working on an issue with a branch name
If the user mentions an issue with a known branch name:
```bash
git checkout trunk && git pull && git checkout -b <branch-name>
```

### Determining diff base for existing PRs
PRs can be stacked — check the actual base:
```bash
PAGER=cat gh pr view <NUMBER> --json baseRefName
```

### Posting review comments
When reviewing or addressing PR feedback:
```bash
# Create review JSON
printf '%s\n' '{
"event": "COMMENT",
"body": "Review comment",
"comments": [
{"path": "file.kt", "line": 42, "body": "Inline comment"}
]
}' > /tmp/pr_review.json

PAGER=cat gh api repos/{owner}/{repo}/pulls/{number}/reviews --method POST --input /tmp/pr_review.json
```
1 change: 1 addition & 0 deletions .claude/skills
139 changes: 139 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Instructions

This file provides guidance to coding agents when working with code in this repository.

## General Approach

- Analyze assumptions and provide counterpoints — prioritize truth over agreement.
- Treat me as an expert Android developer. Give overviews, not tutorials.
- Always give an overview of the solution before diving into implementation (unless explicitly asked to implement right away).
- Do not add comments for trivial logic or when the name is descriptive enough.

## Architecture Overview

**AztecEditor-Android** is a rich-text HTML editor library for Android, built on the `EditText` / `Spannable` API.

**Stack**: Kotlin, Android Spannable API, Plugin architecture, Gradle with Kotlin DSL

### Module Structure

```
:app — Demo application
:aztec — Core editor library (main deliverable)
:glide-loader — Image loading via Glide
:picasso-loader — Image loading via Picasso
:wordpress-comments — WordPress comments plugin
:wordpress-shortcodes — WordPress shortcodes plugin
:media-placeholders — Media placeholders with Compose support
```

All plugin/loader modules depend on `:aztec`. Published independently to Automattic S3 Maven (`org.wordpress:aztec`, `org.wordpress.aztec:*`).

### Code Navigation (package: `org.wordpress.aztec`)

| To find... | Look in... |
|---------------------------------|------------------------------------------------------|
| Main editor component | `aztec/.../AztecText.kt` (extends EditText, ~3K LOC) |
| Editor facade / initialization | `aztec/.../Aztec.kt` |
| HTML parsing | `aztec/.../AztecParser.kt`, `AztecTagHandler.kt` |
| Custom spans (visual rendering) | `aztec/.../spans/` (62 files — one per HTML element) |
| Text formatting logic | `aztec/.../formatting/` (Block, Inline, Line, List) |
| Block element handlers | `aztec/.../handlers/` (Heading, List, Quote, etc.) |
| Plugin interfaces | `aztec/.../plugins/` (IAztecPlugin, IToolbarButton) |
| HTML source editor | `aztec/.../source/SourceViewEditText.kt` |
| Text change watchers | `aztec/.../watchers/` (30+ files, API-version buckets)|
| Toolbar | `aztec/.../toolbar/AztecToolbar.kt` |
| Undo/redo | `aztec/.../History.kt` |
| Compose placeholders | `media-placeholders/.../ComposePlaceholder*.kt` |

### Key Architectural Patterns

1. **Span-based rendering** — Each HTML element has a custom `Span` class. Spans carry both visual rendering and semantic meaning. The `Spannable` API is central to everything.

2. **Plugin architecture**`IAztecPlugin` with sub-interfaces (`IToolbarButton`, `IClipboardPastePlugin`, `IOnDrawPlugin`). Plugins for `html2visual` and `visual2html` conversion pipelines.

3. **Facade pattern**`Aztec` class provides a builder-like fluent API to wire up editor, toolbar, source view, and plugins.

4. **Handler/Formatter split** — Handlers deal with block element structure (headings, lists, quotes). Formatters apply styling (inline, block, line-block, list, link, indent).

5. **Bidirectional HTML conversion** — HTML string <-> Spannable representation, with plugin hooks at each stage.

6. **Watcher pattern** — Multiple `TextWatcher` implementations with API-version-specific buckets (API 25, 26+) for Samsung/OEM compatibility.

### Common Crash Patterns (from recent PRs)

- `IndexOutOfBoundsException` from span start/end being out of bounds — always clamp to `[0, text.length]`
- `IllegalArgumentException` when span end < span start — validate before applying
- Samsung/custom emoji OEM issues causing unexpected span states
- Thread safety in `AztecAttributes` — operations should be synchronized

## Git Operations (CRITICAL)
- **NEVER commit without explicit permission**
- **NEVER push without explicit permission**
- **NEVER create a PR without explicit permission**
- When asked to "fix" or "update" something, that does NOT imply permission to commit/push
- Always wait for explicit "commit", "push", or "create PR" commands

## Build Commands

```bash
# Build and run the demo app
./gradlew :app:installDebug && adb shell am start -n org.wordpress.aztec/org.wordpress.aztec.demo.MainActivity

# Build the demo app (without installing)
./gradlew :app:assembleDebug

# Build the library
./gradlew :aztec:assembleRelease

# Lint (ktlint + Android lint)
./gradlew ktlint
./gradlew lintRelease

# Unit tests (core library)
./gradlew aztec:testRelease

# All unit tests
./gradlew testRelease

# Specific test class
./gradlew :aztec:testReleaseUnitTest --tests "org.wordpress.aztec.AztecParserTest" --info

# Specific test method
./gradlew :aztec:testReleaseUnitTest --tests "org.wordpress.aztec.AztecParserTest.testMethodName" --info
```

### Build Configuration

- All Kotlin warnings are errors (`allWarningsAsErrors = true`)
- Versions are defined in root `build.gradle` — check there for current Kotlin, AGP, SDK, and dependency versions

## GitHub Commands

```bash
PAGER=cat gh pr list
PAGER=cat gh pr view <NUMBER>
PAGER=cat gh pr view <NUMBER> --comments
PAGER=cat gh pr diff <NUMBER>
```

## PR Template

PRs follow this format (from `.github/PULL_REQUEST_TEMPLATE.md`):
```
### Fix
<description of what was fixed/added>
### Test
1. Step 1
2. Step 2
### Review
@reviewer
```

## Skills

| Skill | Trigger phrases |
|-------------|------------------------------------------------------------------------------------------------------|
| `implement` | "implement", "fix this", "work on this", "build this", "add feature", any implementation request |
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ open class MainActivity : AppCompatActivity(),
ToolbarAction.LINK,
ToolbarAction.UNDERLINE,
ToolbarAction.STRIKETHROUGH,
ToolbarAction.REDACTED,
ToolbarAction.ALIGN_LEFT,
ToolbarAction.ALIGN_CENTER,
ToolbarAction.ALIGN_RIGHT,
Expand Down
5 changes: 5 additions & 0 deletions aztec/src/main/kotlin/org/wordpress/aztec/AztecTagHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import org.wordpress.aztec.plugins.html2visual.IHtmlTagHandler
import org.wordpress.aztec.source.CssStyleFormatter
import org.wordpress.aztec.spans.AztecAudioSpan
import org.wordpress.aztec.spans.AztecBackgroundColorSpan
import org.wordpress.aztec.spans.AztecRedactedSpan
import org.wordpress.aztec.spans.AztecHorizontalRuleSpan
import org.wordpress.aztec.spans.AztecImageSpan
import org.wordpress.aztec.spans.AztecListItemSpan
Expand Down Expand Up @@ -176,6 +177,10 @@ class AztecTagHandler(val context: Context, val plugins: List<IAztecPlugin> = Ar

private fun handleBackgroundColorSpanTag(attributes: Attributes, tag: String, nestingLevel: Int): IAztecSpan {
val attrs = AztecAttributes(attributes)
val classAttr = attrs.getValue("class") ?: ""
if (classAttr.contains("redacted") || (tagStack.isNotEmpty() && tagStack.last() is AztecRedactedSpan)) {
return AztecRedactedSpan(attrs)
}
return if (CssStyleFormatter.containsStyleAttribute(
attrs,
CssStyleFormatter.CSS_BACKGROUND_COLOR_ATTRIBUTE
Expand Down
2 changes: 2 additions & 0 deletions aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1441,6 +1441,7 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
AztecTextFormat.FORMAT_CITE,
AztecTextFormat.FORMAT_UNDERLINE,
AztecTextFormat.FORMAT_STRIKETHROUGH,
AztecTextFormat.FORMAT_REDACTED,
AztecTextFormat.FORMAT_BACKGROUND,
AztecTextFormat.FORMAT_HIGHLIGHT,
AztecTextFormat.FORMAT_CODE -> inlineFormatter.toggle(textFormat)
Expand Down Expand Up @@ -1483,6 +1484,7 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
AztecTextFormat.FORMAT_CITE,
AztecTextFormat.FORMAT_UNDERLINE,
AztecTextFormat.FORMAT_STRIKETHROUGH,
AztecTextFormat.FORMAT_REDACTED,
AztecTextFormat.FORMAT_BACKGROUND,
AztecTextFormat.FORMAT_MARK,
AztecTextFormat.FORMAT_HIGHLIGHT,
Expand Down
3 changes: 2 additions & 1 deletion aztec/src/main/kotlin/org/wordpress/aztec/AztecTextFormat.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ enum class AztecTextFormat : ITextFormat {
FORMAT_CODE,
FORMAT_BACKGROUND,
FORMAT_MARK,
FORMAT_HIGHLIGHT
FORMAT_HIGHLIGHT,
FORMAT_REDACTED
}
Loading