Skip to content

Add custom autocomplete rules (tags + suggestions)#239

Merged
FuJacob merged 6 commits into
mainfrom
feat/custom-rules
May 25, 2026
Merged

Add custom autocomplete rules (tags + suggestions)#239
FuJacob merged 6 commits into
mainfrom
feat/custom-rules

Conversation

@FuJacob
Copy link
Copy Markdown
Owner

@FuJacob FuJacob commented May 25, 2026

Summary

Brings back custom AI instructions as rules — short imperative style directives (e.g. "Use British spelling", "Never use em dashes") added as removable tag chips rather than a freeform text block. A small default-on set ships; the rest are tappable suggestions. Rules render as extra prompt directives in both engines, subordinated below the base autocomplete/safety rules.

Stacked on #238 (the cotabby UserDefaults rename). Base this PR on that branch; the new cotabbyCustomRules key follows the new convention. Retarget to main once #238 merges.

What's included

  • CustomRulesCatalogdefaultRules (ship-on, what Reset restores), suggestedPalette, and normalize (trim, case-insensitive dedup, cap 10 × 60 chars).
  • SuggestionSettingsModelcustomRules published property, cotabbyCustomRules persistence with first-run seeding (absent key → seed defaults; present-but-empty → respect cleared), addRule/removeRule/setRules/resetRules, threaded into the snapshot + publisher.
  • PlumbingcustomRules on SuggestionRequest and SuggestionSettingsSnapshot; SuggestionRequestFactory threads it through.
  • Rendering — both LlamaPromptRenderer and FoundationModelPromptRenderer emit a Your style preferences: section after the base rules with an explicit subordination line ("never break the rules above") as a prompt-injection guard. For Foundation Models the rules go in the high-priority instructions channel, not the per-request prompt.
  • UICustomRulesEditor (chips + freeform add + suggestion chips + Reset) in Settings (under Name) and onboarding. Independent of the existing TagsInputView, which is left untouched.

Validation

xcodebuild -project Cotabby.xcodeproj -scheme Cotabby -destination 'platform=macOS' build   # ** BUILD SUCCEEDED **
xcodebuild ... test                                                                          # ** TEST SUCCEEDED **
swiftlint lint --quiet                                                                       # no new warnings

New CustomRulesTests covers normalization (trim/dedup/cap) and renderer placement (rules after base rules; FM rules in instructions not prompt). Registered in project.pbxproj.

Linked issues

Brings back a previously-requested custom-instructions feature.

Risk / rollout notes

  • Rules ship default-on (Write concisely, Match my existing tone). These lightly overlap the base prompt's tone/length rules by design — they exist so Reset has a meaningful target and new users see the feature populated. Easy to trim to empty if you'd rather defaults be silent.
  • Opinionated rules (British spelling, lowercase, formal/casual) are palette-only, never default-on.
  • Rules are local-only (same posture as userName), surfaced in the editor copy.
  • Cap (10 × 60) bounds the local model's prompt budget; normalize is the single chokepoint enforcing it.
  • Global rules only; per-app scoping is a clean follow-up.

Greptile Summary

This PR adds custom autocomplete rules — short imperative style directives stored as removable chip tags — plus a response language selector. Rules are normalized through a single CustomRulesCatalog.normalize chokepoint (trim, case-insensitive dedup, 10 × 60-char cap) and rendered as subordinated bullet directives in both the Llama and Foundation Model prompt backends.

  • New CustomRulesCatalog, SuggestionLanguage, and CustomRulesEditor introduce the full rules pipeline: catalog/normalization → settings persistence → snapshot propagation → request factory threading → renderer emission with a prompt-injection subordination guard.
  • All UserDefaults keys are renamed from the tabby* namespace to cotabby* (continuing the work from Rename all app UserDefaults keys to the cotabby prefix #238), which resets stored settings for existing users; the onboardingCompletedcotabbyOnboardingCompleted rename in WelcomeCoordinator is the highest-impact instance, as it will re-trigger the welcome wizard for every user on upgrade.
  • Tests (CustomRulesTests) cover normalization edge cases and renderer placement for both backends, with clear assertions that rules appear after base rules and only in the sessionInstructions channel for Foundation Models.

Confidence Score: 4/5

Safe to merge after verifying whether the onboarding re-trigger for existing users is intentional.

The rules pipeline, normalization, and prompt rendering are all well-implemented and tested. The one concern is the rename of the onboardingCompleted defaults key to cotabbyOnboardingCompleted in WelcomeCoordinator without migrating the existing value — every user who has already completed onboarding will be shown the welcome wizard again on their next launch. This is a real, present regression. If the team has intentionally chosen to re-onboard all users to surface the new rules/profile step, documenting that decision would close the concern; otherwise a one-time migration read of the old key is needed.

Cotabby/App/Coordinators/WelcomeCoordinator.swift — the onboardingCompleted key rename should be verified as intentional or given a one-time migration.

Important Files Changed

Filename Overview
Cotabby/App/Coordinators/WelcomeCoordinator.swift onboardingCompleted renamed to cotabbyOnboardingCompleted without migrating the existing value — every user who completed onboarding will be shown the welcome screen again on upgrade.
Cotabby/Models/SuggestionSettingsModel.swift Adds customRules and responseLanguage properties with persistence, seeding logic, and mutation helpers. All UserDefaults keys renamed tabby* → cotabby* without migration; onboarding key rename is the highest-impact regression for existing users.
Cotabby/Models/CustomRulesCatalog.swift New file: defines defaultRules (empty), suggestedPalette (12 entries), and normalize (trim/dedup/cap). Logic is clean and deterministic; well-tested.
Cotabby/Models/SuggestionLanguage.swift New enum for 13 languages with BCP-47 raw values, display labels (native + English), and promptInstruction (nil for English). Clean and well-structured.
Cotabby/Support/LlamaPromptRenderer.swift Adds customRules and languageInstruction parameters; rules render after base rules with explicit subordination guard; language directive placed in the high-attention final block. Correct ordering confirmed by tests.
Cotabby/Support/FoundationModelPromptRenderer.swift Language directive and custom rules added to sessionInstructions (not per-request prompt), correctly placed after base rules with subordination guard. Test coverage confirms rules are absent from prompt().
Cotabby/UI/CustomRulesEditor.swift New SwiftUI view with chip display, freeform add, suggestion chips, and Clear affordance. Flow layout is self-contained.
Cotabby/Support/SuggestionRequestFactory.swift Threads customRules and languageInstruction from settings snapshot into both Llama and Foundation model request paths. Correct and complete.
CotabbyTests/CustomRulesTests.swift New test file covering normalize edge cases (trim, dedup, cap, length), renderer placement for both backends, and language instruction ordering. Good coverage.

Sequence Diagram

sequenceDiagram
    participant User
    participant CustomRulesEditor
    participant SuggestionSettingsModel
    participant CustomRulesCatalog
    participant SuggestionRequestFactory
    participant LlamaPromptRenderer
    participant FoundationModelPromptRenderer

    User->>CustomRulesEditor: Add / remove / clear rule
    CustomRulesEditor->>SuggestionSettingsModel: addRule() / removeRule() / clearRules()
    SuggestionSettingsModel->>CustomRulesCatalog: normalize(rules)
    CustomRulesCatalog-->>SuggestionSettingsModel: [String] (trimmed, deduped, capped)
    SuggestionSettingsModel->>SuggestionSettingsModel: persist to UserDefaults (cotabbyCustomRules)

    Note over SuggestionSettingsModel: Publisher emits new SuggestionSettingsSnapshot

    SuggestionRequestFactory->>SuggestionSettingsModel: read snapshot.customRules + responseLanguage

    alt Llama engine
        SuggestionRequestFactory->>LlamaPromptRenderer: prompt(..., customRules, languageInstruction)
        LlamaPromptRenderer-->>SuggestionRequestFactory: prompt with Your style preferences section
    else Foundation Model engine
        SuggestionRequestFactory->>FoundationModelPromptRenderer: sessionInstructions(for: request)
        FoundationModelPromptRenderer-->>SuggestionRequestFactory: instructions with rules + language directive
    end
Loading

Comments Outside Diff (1)

  1. Cotabby/App/Coordinators/WelcomeCoordinator.swift, line 29-56 (link)

    P1 Onboarding re-triggers for all existing users on upgrade

    Renaming the key from "onboardingCompleted" to "cotabbyOnboardingCompleted" without migrating the stored value means any user who completed onboarding will have isOnboardingCompleted return false on the first launch after the update, so presentIfNeeded() will unconditionally show the welcome wizard.

    The comment at line 50–54 justifies skipping the even-older hasShownWelcomeWindow key because that flag was set at presentation time (not completion), so skipping it was intentional. That argument does not apply here — onboardingCompleted is set only at completion, making it a clean proxy for "already set up". If re-showing onboarding for all upgrading users is deliberate (e.g., to introduce the new rules/profile step), it is worth documenting that intent alongside the hasShownWelcomeWindow rationale. If it is unintentional, a one-time migration that reads the old key and writes the new one on first launch would prevent the regression.

    Fix in Codex Fix in Claude Code

Fix All in Codex Fix All in Claude Code

Reviews (4): Last reviewed commit: "Align custom-rules naming and docs to th..." | Re-trigger Greptile

Comment thread Cotabby/UI/CustomRulesEditor.swift
Comment thread Cotabby/Support/FoundationModelPromptRenderer.swift
Comment thread Cotabby/Support/LlamaPromptRenderer.swift
FuJacob added 6 commits May 25, 2026 03:28
Users can now add short imperative style rules (e.g. "Use British
spelling") that render as additional prompt directives. A new default-on
set ships and is what Reset restores.

- CustomRulesCatalog: defaultRules, suggestedPalette, normalize (trim,
  dedup case-insensitive, cap 10 x 60 chars)
- SuggestionSettingsModel: customRules published prop, cotabbyCustomRules
  persistence with first-run seeding, add/remove/set/reset mutators,
  threaded into snapshot + publisher
- SuggestionRequest / SuggestionSettingsSnapshot carry customRules
- Both renderers emit a "Your style preferences:" section after the base
  rules with an explicit subordination line (prompt-injection guard)
- CustomRulesTests covers normalization + render ordering/placement
Removable rule chips, freeform add field (Enter/comma to commit, disabled
at the 10-rule cap), tappable suggestion chips from the palette, and a
Reset that restores the default-on set (shown only when rules differ from
defaults). Wired into the Settings profile section and the onboarding
profile step. Self-contained — does not touch TagsInputView.
defaultRules is now empty — rules are fully opt-in, nothing is applied on
a fresh install. The editor's button becomes "Clear" (restores the empty
default), shown only when rules exist.

Expanded the palette to span tone, length, formatting, locale spelling,
and punctuation so most users find a fitting suggestion without typing
their own. Some entries are intentionally mutually exclusive (casual vs
professional, British vs American) — they're choices, not a stack.
These icon-export source files were accidentally committed; they are not
referenced by the build and don't belong in this feature branch.
…down)

A new Language picker forces the completion language so smaller OSS models
don't drift back to the input's language. English is the default and emits
no prompt directive; any other choice injects "Always write the
continuation in <language>…" — in llama's late Final-instruction block and
in Foundation Models' high-priority instructions channel (where it
supersedes the base "match the existing language" rule).

- SuggestionLanguage enum: 13 popular languages, native + English labels,
  BCP-47-ish raw values, promptInstruction (nil for English)
- SuggestionSettingsModel: responseLanguage + cotabbyResponseLanguage
  persistence, setResponseLanguage, snapshot + publisher
- SuggestionRequest carries languageInstruction; factory computes it
- Both renderers emit the directive; Settings gets a Language picker
- Tests for enum + both renderers
Greptile flagged Clear-vs-Reset inconsistencies. Rules are opt-in with an
empty baseline, so the operation is a clear, not a restore:

- Rename resetRules() -> clearRules() to match the editor's Clear button.
- Fix contradictory/stale doc comments (catalog header, editor header,
  resetRules docstring) that claimed default-on rules and a Reset action.
- Document the first-launch seeding caveat: the unconditional persist writes
  the seed back, so non-empty defaults must be seeded before that write.
@FuJacob FuJacob force-pushed the feat/custom-rules branch from cbc28b1 to bc3fe0f Compare May 25, 2026 10:29
@FuJacob FuJacob changed the base branch from chore/cotabby-userdefaults-keys to main May 25, 2026 10:30
@FuJacob FuJacob merged commit f98063b into main May 25, 2026
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.

1 participant