robertomachorro/moped/code-review

Claude Code and agent skill by robertomachorro/moped

Trust tier 2
  • community
LLM review pending
Source check pending

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-review

SKILL.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:

  1. ./scripts/check_localized_strings.sh
  2. swiftlint --strict over the whole project
  3. swift test --package-path MopedEditor
  4. 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/Text directly —

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:

  1. Preferences — the @objc dynamic var plus its do… reader.
  2. EditorState.makeEditor(model:delegate:) — the initial build.
  3. EditorState.applyPreferences() — the .preferencesChanged observer.
  4. 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) over needsDisplay = true where

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_length is 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 complete in 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 Sendable on

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_length is 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

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
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/robertomachorro/moped/code-review/badge)](https://verified-skill.com/skills/robertomachorro/moped/code-review)

Something wrong with this skill or its page? Tell us.