robertomachorro/moped/code-review
Claude Code and agent skill by robertomachorro/moped
- community
Checking that the SKILL.md file is still in the repository.
Review guidance for Moped, a sandboxed macOS SwiftUI text editor with a homegrown TextKit 1 editor core in the local MopedEditor package. Use when reviewing pull requests in this repository — it carries the invariants that a generic Swift review misses, and the suggestions this project does not want.
Install
npx vskill@latest install robertomachorro/moped/code-reviewSKILL.md
Reviewing Moped
Moped is a document-based macOS app: SwiftUI shell, AppKit editor. The editor is MopedEditor, a local Swift package with zero external dependencies — 3.0.0 deliberately replaced Highlightr and STTextView/Neon with in-house code. Adding a third-party dependency is a design change, not a cleanup; flag any PR that introduces one unless the PR is explicitly about that.
Bias the review toward invariants that are load-bearing but invisible — the ones below have each already caused a real bug here. Ordinary style nits are handled by swiftlint --strict; do not spend the review on them.
The four gates
CI (.github/workflows/build.yaml) runs all of these on every PR, in this order:
./scripts/check_localized_strings.shswiftlint --strictover the whole projectswift test --package-path MopedEditor- the Xcode build
A change is not done until all pass. If a PR touches behavior covered by the package tests and adds none, say so.
Localization
Every user-facing string needs a key in Moped/Localizable.xcstrings. The catalog is at 100% coverage across 13 locales (de, en, es, fi, fr, he, hi, it, ja, nl, pt, pt-BR, uk) — a new key with only English in it silently breaks that, and the check script will not catch it.
Key naming: pref.<setting>.title, pref.section.<section>, option.<group>.<value>, menu.*, alert.*, window.*, status.*, error.*, about.*, default_editor.*. The script's allowlist (scripts/check_localized_strings.sh) rejects anything else.
Where the script is blind, and you should look by hand:
- A string reaching the UI through a variable rather than a literal at the call site.
- A literal passed to a helper rather than to
Toggle/Button/Textdirectly —
PreferencesView.checkbox("pref.…", …) is the standing example. The script's patterns never see it, so a missing catalog key ships as a raw key on screen.
Adding a preference: three legs, and the one that gets forgotten
Preferences (Moped/Preferences.swift) is @unchecked Sendable because it holds no mutable stored state — every property reads and writes UserDefaults. A stored property added to it invalidates that annotation. Flag one.
Booleans are stored as the strings "Yes" / "No" with a do…-prefixed Bool reader. This is deliberate and long-standing; do not suggest converting them to Bool or @AppStorage.
A new editor-affecting preference must be wired in all of:
Preferences— the@objc dynamic varplus itsdo…reader.EditorState.makeEditor(model:delegate:)— the initial build.EditorState.applyPreferences()— the.preferencesChangedobserver.PreferencesView— the row, with a catalog key.
Legs 2 and 3 are separate call sites with no shared helper. Missing #3 means the setting only takes effect when some other preference is later written; missing #2 means it only takes effect on an already-open window. Check for both explicitly — this is the single most likely defect in a settings PR.
The editor core
MopedTextView is an NSTextView on an explicit TextKit 1 stack. Temporary attributes and the NSRulerView gutter both depend on NSLayoutManager; "why not TextKit 2" is not a useful review comment.
- Never write rendering state into `NSTextStorage`. Token colors live as layout-manager
temporary attributes precisely so they cannot enter the text, land on the undo stack, or mark the document dirty. A PR that adds an attribute to the storage for display purposes is a bug, however well it renders.
- `SyntaxHighlighter` owns two exclusive resources:
textStorage.delegate(one slot
only) and the .foregroundColor temporary attribute, which it clears and rewrites over the whole edited range on every pass. Anything else writing that attribute will be silently wiped.
- `allowsNonContiguousLayout` is on. Any self-computed visible range must call
ensureLayout(forBoundingRect:in:) first, or the glyph range comes back short for a region scrolled into for the first time — the gutter shipped that bug once.
- But never force layout from inside a draw, and never mutate rendering state inside
NSTextStorage.processEditing. Both re-enter TextKit underneath code that has already captured geometry. The established fix is to defer to the next runloop turn (LineNumberRulerView.updateThickness, SyntaxHighlighter.schedulePass).
- Prefer partial invalidation.
setNeedsDisplay(rect)overneedsDisplay = truewhere
a bounded region is knowable — a full redraw on every caret move made cursor movement scale with document size. A whole-view invalidation needs a reason in a comment.
- Drawing order matters. The layout manager paints selection and find-bar highlights in
drawBackground(forGlyphRange:at:), before glyphs. Decorations that must survive a selection belong after super.drawGlyphs, not in a background override.
Themes
MopedTheme is Sendable only because every color it carries is a plain sRGB component color. A theme built from NSColor.controlAccentColor or any catalog/dynamic system color reintroduces deferred appearance resolution and breaks the guarantee. Flag it.
Adding a color to MopedTheme is not a small change: it touches the struct, renamed(_:), paired(withDark:), all seven files in Themes/, the System theme, the .mopedtheme read/write maps and fileFormatVersion in EditorTheme+File.swift, plus ThemeFileTests. If a PR can derive its color from an existing one instead (the gutter separator and the whitespace markers both use the palette's own foreground at 30% alpha), prefer that and say so.
Painted colors are pushed out from MopedTextView.applyTheme(), which is the single funnel reached by both a theme change and viewDidChangeEffectiveAppearance(). A new painted color set anywhere else will not survive a light/dark flip.
Conventions
- Tabs for indentation, in Swift and in tests.
line_lengthis disabled but long lines
are still unwelcome.
- Doc comments explain *why*, not *what*. The house style names the bug or constraint
that forced the code to be the way it is. A comment restating the signature adds nothing; a non-obvious decision with no comment is worth flagging.
- Tests name the bug they pin down, and assertion messages are full sentences
explaining the failure. Logic that would otherwise only run inside a graphics context is split into a testable non-drawing method — LineNumberRulerView.visibleLineNumbers(for:) and WhitespaceLayoutManager.whitespaceMarkers(forGlyphRange:) are the precedents.
- Strict concurrency is
completein the app target, and the package mirrors it via
.enableExperimentalFeature("StrictConcurrency") so swift test checks the same rules.
- User-visible changes update the docs:
DOCUMENTATION.md(including its settings
tables), CHANGELOG.md, and manual-checklist.md for anything only a human can verify. A feature PR that touches none of them is probably incomplete.
- The Settings window has a fixed, measured
.frame(width: 590, height: 246), sized
against the widest locale. The comment above it is a measurement log — a PR that changes the frame without extending that comment, or that adds a pane row without saying whether it still fits, deserves a question.
- Printing (
SourcePrintView) is a separate `CTFramesetter` path that does not share
the editor's layout manager. New editor rendering does not print automatically; if a PR implies otherwise, check.
Do not raise these
They are settled decisions, and re-litigating them costs the maintainer time:
- Tabs instead of spaces; the
"Yes"/"No"preference encoding;@unchecked Sendableon
Preferences; TextKit 1 instead of TextKit 2.
- "Consider extracting this into a protocol/abstraction" for single-use code. This project
wants the minimum code that solves the problem — no speculative abstraction.
- Line length, given
line_lengthis disabled in.swiftlint.yml. - Suggesting a third-party library for something the package already does by hand.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryChecking.github/skills/code-review/SKILL.md
Popularity
- Repository stars
- 113
- Repository forks
- 8
- 7-day trend score
- 24
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/robertomachorro/moped/code-review)