diff --git a/CATALOG.md b/CATALOG.md index bc2885fa..a77dd226 100644 --- a/CATALOG.md +++ b/CATALOG.md @@ -2,9 +2,9 @@ Generated at: 2026-02-08T00:00:00.000Z -Total skills: 1311 +Total skills: 1326 -## architecture (87) +## architecture (88) | Skill | Description | Tags | Triggers | | --- | --- | --- | --- | @@ -83,6 +83,7 @@ Total skills: 1311 | `site-architecture` | Plan or restructure website hierarchy, navigation, URL patterns, breadcrumbs, and internal linking. Use when mapping pages, sections, and site structure, but... | site, architecture | site, architecture, plan, restructure, website, hierarchy, navigation, url, breadcrumbs, internal, linking, mapping | | `slack-bot-builder` | The Bolt framework is Slack's recommended approach for building apps. It handles authentication, event routing, request verification, and HTTP request proces... | slack, bot, builder | slack, bot, builder, bolt, framework, recommended, approach, building, apps, authentication, event, routing | | `software-architecture` | Guide for quality focused software architecture. This skill should be used when users want to write code, design architecture, analyze code, in any case that... | software, architecture | software, architecture, quality, skill, should, used, users, want, write, code, analyze, any | +| `swiftui-ui-patterns` | Apply proven SwiftUI UI patterns for navigation, sheets, async state, and reusable screens. | swiftui, ui | swiftui, ui, apply, proven, navigation, sheets, async, state, reusable, screens | | `tailwind-design-system` | Build production-ready design systems with Tailwind CSS, including design tokens, component variants, responsive patterns, and accessibility. | tailwind | tailwind, css, including, tokens, component, variants, responsive, accessibility | | `tailwind-patterns` | Tailwind CSS v4 principles. CSS-first configuration, container queries, modern patterns, design token architecture. | tailwind | tailwind, css, v4, principles, first, configuration, container, queries, token, architecture | | `testing-patterns` | Jest testing patterns, factory functions, mocking strategies, and TDD workflow. Use when writing unit tests, creating test factories, or following TDD red-gr... | | testing, jest, factory, functions, mocking, tdd, writing, unit, tests, creating, test, factories | @@ -170,7 +171,7 @@ Total skills: 1311 | `warren-buffett` | Agente que simula Warren Buffett — o maior investidor do seculo XX e XXI, CEO da Berkshire Hathaway, discipulo de Benjamin Graham e socio intelectual de Char... | persona, investing, value-investing, business | persona, investing, value-investing, business, warren, buffett, agente, que, simula, maior, investidor, do | | `whatsapp-automation` | Automate WhatsApp Business tasks via Rube MCP (Composio): send messages, manage templates, upload media, and handle contacts. Always search tools first for c... | whatsapp | whatsapp, automation, automate, business, tasks, via, rube, mcp, composio, send, messages, upload | -## data-ai (249) +## data-ai (250) | Skill | Description | Tags | Triggers | | --- | --- | --- | --- | @@ -400,6 +401,7 @@ Total skills: 1311 | `stability-ai` | Geracao de imagens via Stability AI (SD3.5, Ultra, Core). Text-to-image, img2img, inpainting, upscale, remove-bg, search-replace. 15 estilos artisticos. | image-generation, stable-diffusion, ai-art, api | image-generation, stable-diffusion, ai-art, api, stability, ai, geracao, de, imagens, via, sd3, ultra | | `stitch-ui-design` | Expert guidance for crafting effective prompts in Google Stitch, the AI-powered UI design tool by Google Labs. This skill helps create precise, actionable pr... | stitch, ui | stitch, ui, guidance, crafting, effective, prompts, google, ai, powered, labs, skill, helps | | `supabase-automation` | Automate Supabase database queries, table management, project administration, storage, edge functions, and SQL execution via Rube MCP (Composio). Always sear... | supabase | supabase, automation, automate, database, queries, table, administration, storage, edge, functions, sql, execution | +| `swiftui-view-refactor` | Refactor SwiftUI views into smaller components with stable, explicit data flow. | swiftui, view, refactor | swiftui, view, refactor, views, smaller, components, stable, explicit, data, flow | | `tanstack-query-expert` | Expert in TanStack Query (React Query) — asynchronous state management. Covers data fetching, stale time configuration, mutations, optimistic updates, and Ne... | tanstack, query | tanstack, query, react, asynchronous, state, covers, data, fetching, stale, time, configuration, mutations | | `team-collaboration-standup-notes` | You are an expert team communication specialist focused on async-first standup practices, AI-assisted note generation from commit history, and effective remo... | team, collaboration, standup, notes | team, collaboration, standup, notes, communication, async, first, ai, assisted, note, generation, commit | | `travel-health-analyzer` | 分析旅行健康数据、评估目的地健康风险、提供疫苗接种建议、生成多语言紧急医疗信息卡片。支持WHO/CDC数据集成的专业级旅行健康风险评估。 | travel, health, analyzer | travel, health, analyzer, who, cdc | @@ -424,7 +426,7 @@ Total skills: 1311 | `youtube-automation` | Automate YouTube tasks via Rube MCP (Composio): upload videos, manage playlists, search content, get analytics, and handle comments. Always search tools firs... | youtube | youtube, automation, automate, tasks, via, rube, mcp, composio, upload, videos, playlists, search | | `zapier-make-patterns` | You are a no-code automation architect who has built thousands of Zaps and Scenarios for businesses of all sizes. You've seen automations that save companies... | zapier, make | zapier, make, no, code, automation, architect, who, built, thousands, zaps, scenarios, businesses | -## development (178) +## development (181) | Skill | Description | Tags | Triggers | | --- | --- | --- | --- | @@ -522,6 +524,7 @@ Total skills: 1311 | `frontend-slides` | Create stunning, animation-rich HTML presentations from scratch or by converting PowerPoint files. | frontend, slides | frontend, slides, stunning, animation, rich, html, presentations, scratch, converting, powerpoint, files | | `game-development/mobile-games` | Mobile game development principles. Touch input, battery, performance, app stores. | game, development/mobile, games | game, development/mobile, games, mobile, development, principles, touch, input, battery, performance, app, stores | | `gemini-api-integration` | Use when integrating Google Gemini API into projects. Covers model selection, multimodal inputs, streaming, function calling, and production best practices. | gemini, api, integration | gemini, api, integration, integrating, google, covers, model, selection, multimodal, inputs, streaming, function | +| `github` | Use the `gh` CLI for issues, pull requests, Actions runs, and GitHub API queries. | github | github, gh, cli, issues, pull, requests, actions, runs, api, queries | | `go-concurrency-patterns` | Master Go concurrency with goroutines, channels, sync primitives, and context. Use when building concurrent Go applications, implementing worker pools, or de... | go, concurrency | go, concurrency, goroutines, channels, sync, primitives, context, building, concurrent, applications, implementing, worker | | `go-playwright` | Expert capability for robust, stealthy, and efficient browser automation using Playwright Go. | go, playwright | go, playwright, capability, robust, stealthy, efficient, browser, automation | | `go-rod-master` | Comprehensive guide for browser automation and web scraping with go-rod (Chrome DevTools Protocol) including stealth anti-bot-detection patterns. | go, rod, master | go, rod, master, browser, automation, web, scraping, chrome, devtools, protocol, including, stealth | @@ -530,6 +533,7 @@ Total skills: 1311 | `hugging-face-dataset-viewer` | Use this skill for Hugging Face Dataset Viewer API workflows that fetch subset/split metadata, paginate rows, search text, apply filters, download parquet UR... | hugging, face, dataset, viewer | hugging, face, dataset, viewer, skill, api, fetch, subset, split, metadata, paginate, rows | | `hugging-face-evaluation` | Add and manage evaluation results in Hugging Face model cards. Supports extracting eval tables from README content, importing scores from Artificial Analysis... | hugging, face, evaluation | hugging, face, evaluation, add, results, model, cards, supports, extracting, eval, tables, readme | | `hugging-face-tool-builder` | Your purpose is now is to create reusable command line scripts and utilities for using the Hugging Face API, allowing chaining, piping and intermediate proce... | hugging, face, builder | hugging, face, builder, purpose, now, reusable, command, line, scripts, utilities, api, allowing | +| `ios-debugger-agent` | Debug the current iOS project on a booted simulator with XcodeBuildMCP. | ios, debugger, agent | ios, debugger, agent, debug, current, booted, simulator, xcodebuildmcp | | `javascript-mastery` | 33+ essential JavaScript concepts every developer should know, inspired by [33-js-concepts](https://github.com/leonardomso/33-js-concepts). | javascript, mastery | javascript, mastery, 33, essential, concepts, every, developer, should, know, inspired, js, https | | `javascript-pro` | Master modern JavaScript with ES6+, async patterns, and Node.js APIs. Handles promises, event loops, and browser/Node compatibility. | javascript | javascript, pro, es6, async, node, js, apis, promises, event, loops, browser, compatibility | | `javascript-testing-patterns` | Comprehensive guide for implementing robust testing strategies in JavaScript/TypeScript applications using modern testing frameworks and best practices. | javascript | javascript, testing, implementing, robust, typescript, applications, frameworks | @@ -570,6 +574,7 @@ Total skills: 1311 | `python-performance-optimization` | Profile and optimize Python code using cProfile, memory profilers, and performance best practices. Use when debugging slow Python code, optimizing bottleneck... | python, performance, optimization | python, performance, optimization, profile, optimize, code, cprofile, memory, profilers, debugging, slow, optimizing | | `python-pro` | Master Python 3.12+ with modern features, async programming, performance optimization, and production-ready practices. Expert in the latest Python ecosystem ... | python | python, pro, 12, features, async, programming, performance, optimization, latest, ecosystem, including, uv | | `python-testing-patterns` | Implement comprehensive testing strategies with pytest, fixtures, mocking, and test-driven development. Use when writing Python tests, setting up test suites... | python | python, testing, pytest, fixtures, mocking, test, driven, development, writing, tests, setting, up | +| `react-component-performance` | Diagnose slow React components and suggest targeted performance fixes. | react, component, performance | react, component, performance, diagnose, slow, components, suggest, targeted, fixes | | `react-flow-architect` | Build production-ready ReactFlow applications with hierarchical navigation, performance optimization, and advanced state management. | react, flow | react, flow, architect, reactflow, applications, hierarchical, navigation, performance, optimization, state | | `react-flow-node-ts` | Create React Flow node components following established patterns with proper TypeScript types and store integration. | react, flow, node, ts | react, flow, node, ts, components, following, established, proper, typescript, types, store, integration | | `react-modernization` | Master React version upgrades, class to hooks migration, concurrent features adoption, and codemods for automated transformation. | react, modernization | react, modernization, version, upgrades, class, hooks, migration, concurrent, features, adoption, codemods, automated | @@ -607,7 +612,7 @@ Total skills: 1311 | `zod-validation-expert` | Expert in Zod — TypeScript-first schema validation. Covers parsing, custom errors, refinements, type inference, and integration with React Hook Form, Next.js... | zod, validation | zod, validation, typescript, first, schema, covers, parsing, custom, errors, refinements, type, inference | | `zustand-store-ts` | Create Zustand stores following established patterns with proper TypeScript types and middleware. | zustand, store, ts | zustand, store, ts, stores, following, established, proper, typescript, types, middleware | -## general (317) +## general (325) | Skill | Description | Tags | Triggers | | --- | --- | --- | --- | @@ -631,6 +636,7 @@ Total skills: 1311 | `apify-trend-analysis` | Discover and track emerging trends across Google Trends, Instagram, Facebook, YouTube, and TikTok to inform content strategy. | apify, trend | apify, trend, analysis, discover, track, emerging, trends, google, instagram, facebook, youtube, tiktok | | `app-builder` | Main application building orchestrator. Creates full-stack applications from natural language requests. Determines project type, selects tech stack, coordina... | app, builder | app, builder, main, application, building, orchestrator, creates, full, stack, applications, natural, language | | `app-builder/templates` | Project scaffolding templates for new applications. Use when creating new projects from scratch. Contains 12 templates for various tech stacks. | app, builder/templates | app, builder/templates, scaffolding, new, applications, creating, scratch, contains, 12, various, tech, stacks | +| `app-store-changelog` | Generate user-facing App Store release notes from git history since the last tag. | app, store, changelog | app, store, changelog, generate, user, facing, release, notes, git, history, since, last | | `arm-cortex-expert` | Senior embedded software engineer specializing in firmware and driver development for ARM Cortex-M microcontrollers (Teensy, STM32, nRF52, SAMD). | arm, cortex | arm, cortex, senior, embedded, software, engineer, specializing, firmware, driver, development, microcontrollers, teensy | | `ask-questions-if-underspecified` | Clarify requirements before implementing. Use when serious doubts arise. | ask, questions, if, underspecified | ask, questions, if, underspecified, clarify, requirements, before, implementing, serious, doubts, arise | | `avalonia-layout-zafiro` | Guidelines for modern Avalonia UI layout using Zafiro.Avalonia, emphasizing shared styles, generic components, and avoiding XAML redundancy. | avalonia, layout, zafiro | avalonia, layout, zafiro, guidelines, ui, emphasizing, shared, styles, generic, components, avoiding, xaml | @@ -790,6 +796,8 @@ Total skills: 1311 | `linear-claude-skill` | Manage Linear issues, projects, and teams | linear, claude, skill | linear, claude, skill, issues, teams | | `lint-and-validate` | MANDATORY: Run appropriate validation tools after EVERY code change. Do not finish a task until the code is error-free. | lint, and, validate | lint, and, validate, mandatory, run, appropriate, validation, after, every, code, change, do | | `logistics-exception-management` | Codified expertise for handling freight exceptions, shipment delays, damages, losses, and carrier disputes. Informed by logistics professionals with 15+ year... | logistics, exception | logistics, exception, codified, expertise, handling, freight, exceptions, shipment, delays, damages, losses, carrier | +| `macos-menubar-tuist-app` | Build, refactor, or review SwiftUI macOS menubar apps that use Tuist. | macos, menubar, tuist, app | macos, menubar, tuist, app, refactor, review, swiftui, apps | +| `macos-spm-app-packaging` | Scaffold, build, sign, and package SwiftPM macOS apps without Xcode projects. | macos, spm, app, packaging | macos, spm, app, packaging, scaffold, sign, package, swiftpm, apps, without, xcode | | `magic-ui-generator` | Utilizes Magic by 21st.dev to generate, compare, and integrate multiple production-ready UI component variations. | magic, ui, generator | magic, ui, generator, utilizes, 21st, dev, generate, compare, integrate, multiple, component, variations | | `makepad-animation` | CRITICAL: Use for Makepad animation system. Triggers on: makepad animation, makepad animator, makepad hover, makepad state, makepad transition, "from: { all:... | makepad, animation | makepad, animation, critical, triggers, animator, hover, state, transition, all, forward, pressed | | `makepad-basics` | CRITICAL: Use for Makepad getting started and app structure. Triggers on: makepad, makepad getting started, makepad tutorial, live_design!, app_main!, makepa... | makepad, basics | makepad, basics, critical, getting, started, app, structure, triggers, tutorial, live, main, setup | @@ -826,6 +834,7 @@ Total skills: 1311 | `odoo-xml-views-builder` | Expert at building Odoo XML views: Form, List, Kanban, Search, Calendar, and Graph. Generates correct XML for Odoo 14-17 with proper visibility syntax. | odoo, xml, views, builder | odoo, xml, views, builder, building, form, list, kanban, search, calendar, graph, generates | | `onboarding-cro` | You are an expert in user onboarding and activation. Your goal is to help users reach their "aha moment" as quickly as possible and establish habits that lea... | onboarding, cro | onboarding, cro, user, activation, goal, users, reach, aha, moment, quickly, possible, establish | | `oral-health-analyzer` | 分析口腔健康数据、识别口腔问题模式、评估口腔健康状况、提供个性化口腔健康建议。支持与营养、慢性病、用药等其他健康数据的关联分析。 | oral, health, analyzer | oral, health, analyzer | +| `orchestrate-batch-refactor` | Plan and execute large refactors with dependency-aware work packets and parallel analysis. | orchestrate, batch, refactor | orchestrate, batch, refactor, plan, execute, large, refactors, dependency, aware, work, packets, parallel | | `oss-hunter` | Automatically hunt for high-impact OSS contribution opportunities in trending repositories. | oss, hunter | oss, hunter, automatically, hunt, high, impact, contribution, opportunities, trending, repositories | | `page-cro` | Analyze and optimize individual pages for conversion performance. | page, cro | page, cro, analyze, optimize, individual, pages, conversion, performance | | `paypal-integration` | Master PayPal payment integration including Express Checkout, IPN handling, recurring billing, and refund workflows. | paypal, integration | paypal, integration, payment, including, express, checkout, ipn, handling, recurring, billing, refund | @@ -843,6 +852,7 @@ Total skills: 1311 | `pr-writer` | Create pull requests following Sentry's engineering practices. | pr, writer | pr, writer, pull, requests, following, sentry, engineering | | `production-scheduling` | Codified expertise for production scheduling, job sequencing, line balancing, changeover optimisation, and bottleneck resolution in discrete and batch manufa... | production, scheduling | production, scheduling, codified, expertise, job, sequencing, line, balancing, changeover, optimisation, bottleneck, resolution | | `professional-proofreader` | Use when a user asks to "proofread", "review and correct", "fix grammar", "improve readability while keeping my voice", and to proofread a document file and ... | professional, proofreader | professional, proofreader, user, asks, proofread, review, correct, fix, grammar, improve, readability, while | +| `project-skill-audit` | Audit a project and recommend the highest-value skills to add or update. | skill, audit | skill, audit, recommend, highest, value, skills, add, update | | `prompt-engineer` | Transforms user prompts into optimized prompts using frameworks (RTF, RISEN, Chain of Thought, RODES, Chain of Density, RACE, RISE, STAR, SOAP, CLEAR, GROW) | [prompt-engineering, optimization, frameworks, ai-enhancement] | [prompt-engineering, optimization, frameworks, ai-enhancement], prompt, engineer, transforms, user, prompts, optimized, rtf, risen | | `prompt-library` | A comprehensive collection of battle-tested prompts inspired by [awesome-chatgpt-prompts](https://github.com/f/awesome-chatgpt-prompts) and community best pr... | prompt, library | prompt, library, collection, battle, tested, prompts, inspired, awesome, chatgpt, https, github, com | | `qiskit` | Qiskit is the world's most popular open-source quantum computing framework with 13M+ downloads. Build quantum circuits, optimize for hardware, execute on sim... | qiskit | qiskit, world, most, popular, open, source, quantum, computing, framework, 13m, downloads, circuits | @@ -880,6 +890,9 @@ Total skills: 1311 | `stitch-loop` | Teaches agents to iteratively build websites using Stitch with an autonomous baton-passing loop pattern | stitch, loop | stitch, loop, teaches, agents, iteratively, websites, autonomous, baton, passing | | `subagent-driven-development` | Use when executing implementation plans with independent tasks in the current session | subagent, driven | subagent, driven, development, executing, plans, independent, tasks, current, session | | `superpowers-lab` | Lab environment for Claude superpowers | superpowers, lab | superpowers, lab, environment, claude | +| `swift-concurrency-expert` | Review and fix Swift concurrency issues such as actor isolation and Sendable violations. | swift, concurrency | swift, concurrency, review, fix, issues, such, actor, isolation, sendable, violations | +| `swiftui-liquid-glass` | Implement or review SwiftUI Liquid Glass APIs with correct fallbacks and modifier order. | swiftui, liquid, glass | swiftui, liquid, glass, review, apis, correct, fallbacks, modifier, order | +| `swiftui-performance-audit` | Audit SwiftUI performance issues from code review and profiling evidence. | swiftui, performance, audit | swiftui, performance, audit, issues, code, review, profiling, evidence | | `tcm-constitution-analyzer` | 分析中医体质数据、识别体质类型、评估体质特征,并提供个性化养生建议。支持与营养、运动、睡眠等健康数据的关联分析。 | tcm, constitution, analyzer | tcm, constitution, analyzer | | `team-composition-analysis` | Design optimal team structures, hiring plans, compensation strategies, and equity allocation for early-stage startups from pre-seed through Series A. | team, composition | team, composition, analysis, optimal, structures, hiring, plans, compensation, equity, allocation, early, stage | | `telegram-bot-builder` | You build bots that people actually use daily. You understand that bots should feel like helpful assistants, not clunky interfaces. You know the Telegram eco... | telegram, bot, builder | telegram, bot, builder, bots, people, actually, daily, understand, should, feel, like, helpful | @@ -1053,7 +1066,7 @@ Total skills: 1311 | `whatsapp-cloud-api` | Integracao com WhatsApp Business Cloud API (Meta). Mensagens, templates, webhooks HMAC-SHA256, automacao de atendimento. Boilerplates Node.js e Python. | messaging, whatsapp, meta, webhooks | messaging, whatsapp, meta, webhooks, cloud, api, integracao, com, business, mensagens, hmac, sha256 | | `x-twitter-scraper` | X (Twitter) data platform skill — tweet search, user lookup, follower extraction, engagement metrics, giveaway draws, monitoring, webhooks, 19 extraction too... | [twitter, x-api, scraping, mcp, social-media, data-extraction, giveaway, monitoring, webhooks] | [twitter, x-api, scraping, mcp, social-media, data-extraction, giveaway, monitoring, webhooks], twitter, scraper, data | -## security (162) +## security (164) | Skill | Description | Tags | Triggers | | --- | --- | --- | --- | @@ -1191,9 +1204,11 @@ Total skills: 1311 | `seo-forensic-incident-response` | Investigate sudden drops in organic traffic or rankings and run a structured forensic SEO incident response with triage, root-cause analysis and recovery plan. | seo, forensic, incident, response | seo, forensic, incident, response, investigate, sudden, drops, organic, traffic, rankings, run, structured | | `seo-technical` | Audit technical SEO across crawlability, indexability, security, URLs, mobile, Core Web Vitals, structured data, JavaScript rendering, and related platform s... | seo, technical | seo, technical, audit, crawlability, indexability, security, urls, mobile, core, web, vitals, structured | | `service-mesh-expert` | Expert service mesh architect specializing in Istio, Linkerd, and cloud-native networking patterns. Masters traffic management, security policies, observabil... | service, mesh | service, mesh, architect, specializing, istio, linkerd, cloud, native, networking, masters, traffic, security | +| `simplify-code` | Review a diff for clarity and safe simplifications, then optionally apply low-risk fixes. | simplify, code | simplify, code, review, diff, clarity, safe, simplifications, then, optionally, apply, low, risk | | `skill-creator` | To create new CLI skills following Anthropic's official best practices with zero manual configuration. This skill automates brainstorming, template applicati... | [automation, scaffolding, skill-creation, meta-skill] | [automation, scaffolding, skill-creation, meta-skill], skill, creator, new, cli, skills, following, anthropic, official | | `skill-scanner` | Scan agent skills for security issues before adoption. Detects prompt injection, malicious code, excessive permissions, secret exposure, and supply chain risks. | skill, scanner | skill, scanner, scan, agent, skills, security, issues, before, adoption, detects, prompt, injection | | `smtp-penetration-testing` | Conduct comprehensive security assessments of SMTP (Simple Mail Transfer Protocol) servers to identify vulnerabilities including open relays, user enumeratio... | smtp, penetration | smtp, penetration, testing, conduct, security, assessments, simple, mail, transfer, protocol, servers, identify | +| `snowflake-development` | Comprehensive Snowflake development assistant covering SQL best practices, data pipeline design (Dynamic Tables, Streams, Tasks, Snowpipe), Cortex AI functio... | snowflake | snowflake, development, assistant, covering, sql, data, pipeline, dynamic, tables, streams, tasks, snowpipe | | `solidity-security` | Master smart contract security best practices, vulnerability prevention, and secure Solidity development patterns. | solidity, security | solidity, security, smart, contract, vulnerability, prevention, secure, development | | `spec-to-code-compliance` | Verifies code implements exactly what documentation specifies for blockchain audits. Use when comparing code against whitepapers, finding gaps between specs ... | spec, to, code, compliance | spec, to, code, compliance, verifies, implements, exactly, what, documentation, specifies, blockchain, audits | | `sql-injection-testing` | Execute comprehensive SQL injection vulnerability assessments on web applications to identify database security flaws, demonstrate exploitation techniques, a... | sql, injection | sql, injection, testing, execute, vulnerability, assessments, web, applications, identify, database, security, flaws | diff --git a/CHANGELOG.md b/CHANGELOG.md index fb8e79c2..71b91794 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,59 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [8.9.0] - 2026-03-25 - "Apple Workflow Expansion and Data Platform Additions" + +> Installable skill library update for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and related AI coding assistants. + +Start here: + +- Install: `npx antigravity-awesome-skills` +- Choose your tool: [README -> Choose Your Tool](https://github.com/sickn33/antigravity-awesome-skills#choose-your-tool) +- Best skills by tool: [README -> Best Skills By Tool](https://github.com/sickn33/antigravity-awesome-skills#best-skills-by-tool) +- Bundles: [docs/users/bundles.md](https://github.com/sickn33/antigravity-awesome-skills/blob/main/docs/users/bundles.md) +- Workflows: [docs/users/workflows.md](https://github.com/sickn33/antigravity-awesome-skills/blob/main/docs/users/workflows.md) + +This release combines a curated import from `Dimillian/Skills` with two merged community pull requests on `main`. It expands Apple-platform workflows, GitHub/refactor guidance, and SwiftUI performance coverage, adds a new Snowflake engineering skill, updates WordPress skills for 7.0, and refreshes the registry/docs surface to `1,326+` indexed skills. + +## New Skills + +- **app-store-changelog** - turn git history into concise, user-facing App Store release notes. +- **github** - use the `gh` CLI for pull requests, issues, workflow runs, and GitHub API queries. +- **ios-debugger-agent** - debug iOS apps on booted simulators with XcodeBuildMCP. +- **macos-menubar-tuist-app** - build and maintain SwiftUI macOS menubar apps with Tuist-first workflows. +- **macos-spm-app-packaging** - scaffold and package SwiftPM macOS apps without Xcode projects. +- **orchestrate-batch-refactor** - plan large refactors with dependency-aware work packets and parallel analysis. +- **project-skill-audit** - audit a project and recommend the highest-value skills to add or update. +- **react-component-performance** - diagnose slow React components and apply targeted performance fixes. +- **simplify-code** - review diffs for clarity and safe simplifications. +- **snowflake-development** - Snowflake SQL, pipelines, Cortex AI, Snowpark, performance, and security guidance (PR #395). +- **swift-concurrency-expert** - fix actor isolation, `Sendable`, and Swift concurrency issues. +- **swiftui-liquid-glass** - implement and review SwiftUI Liquid Glass APIs correctly. +- **swiftui-performance-audit** - audit SwiftUI runtime performance from code and profiling evidence. +- **swiftui-ui-patterns** - apply proven SwiftUI patterns for navigation, sheets, and async state. +- **swiftui-view-refactor** - refactor SwiftUI views into smaller components with explicit data flow. + +## Improvements + +- **WordPress 7.0 coverage**: merged PR #394 to expand `wordpress`, `wordpress-plugin-development`, `wordpress-theme-development`, `wordpress-woocommerce-development`, and `wordpress-penetration-testing` with WordPress 7.0 collaboration, AI, admin, and security guidance. +- **Maintainer PR flow**: brought both open PRs into compliance with the source-only policy and PR template requirements before merging them via GitHub squash merge. +- **Registry sync**: refreshed README/catalog metadata, contributor sync, and count-sensitive docs so `main` now reflects `1,326+` indexed skills. +- **Warning-budget preservation**: normalized imported and newly merged skills so the repository remains within the frozen validation budget at `135/135`. + +## Who should care + +- **Claude Code and Codex CLI users** get a larger set of high-signal workflow skills for GitHub, refactoring, project audits, and Swift/SwiftUI maintenance. +- **Apple-platform developers** get a meaningful jump in coverage across iOS debugging, Swift concurrency, SwiftUI architecture, Liquid Glass, performance, and macOS packaging/menubar patterns. +- **Data and platform engineers** get a new `snowflake-development` skill plus richer WordPress 7.0 documentation for modern content/admin workflows. +- **Maintainers** benefit from a clean post-merge registry state, contributor sync, and release-ready validation posture. + +## Credits + +- **[@jamescha-earley](https://github.com/jamescha-earley)** for the new `snowflake-development` skill in PR #395 +- **[@munir-abbasi](https://github.com/munir-abbasi)** for the WordPress 7.0 documentation update in PR #394 + +Upgrade now: `git pull origin main` to fetch the latest skills. + ## [8.8.0] - 2026-03-24 - "Review Automation and Research Expansion" > Installable skill library update for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and related AI coding assistants. diff --git a/README.md b/README.md index cb46a59d..68b1043a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ - -# 🌌 Antigravity Awesome Skills: 1,311+ Agentic Skills for Claude Code, Gemini CLI, Cursor, Copilot & More + +# 🌌 Antigravity Awesome Skills: 1,326+ Agentic Skills for Claude Code, Gemini CLI, Cursor, Copilot & More -> **Installable GitHub library of 1,311+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and other AI coding assistants.** +> **Installable GitHub library of 1,326+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and other AI coding assistants.** Antigravity Awesome Skills is a GitHub repository and installer CLI for reusable `SKILL.md` playbooks. Instead of collecting random prompts, you get a searchable, installable skill library for planning, coding, debugging, testing, security review, infrastructure work, product workflows, and growth tasks across the major AI coding assistants. @@ -26,7 +26,7 @@ Antigravity Awesome Skills is a GitHub repository and installer CLI for reusable - **Installable, not just inspirational**: use `npx antigravity-awesome-skills` to put skills where your tool expects them. - **Built for major agent workflows**: Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, Kiro, OpenCode, Copilot, and more. -- **Broad coverage with real utility**: 1,311+ skills across development, testing, security, infrastructure, product, and marketing. +- **Broad coverage with real utility**: 1,326+ skills across development, testing, security, infrastructure, product, and marketing. - **Faster onboarding**: bundles and workflows reduce the time from "I found this repo" to "I used my first skill". - **Useful whether you want breadth or curation**: browse the full catalog, start with top bundles, or compare alternatives before installing. @@ -43,7 +43,7 @@ Antigravity Awesome Skills is a GitHub repository and installer CLI for reusable - [🧭 Antigravity Workflows](#antigravity-workflows) - [⚖️ Alternatives & Comparisons](#alternatives--comparisons) - [📦 Features & Categories](#features--categories) -- [📚 Browse 1,311+ Skills](#browse-1311-skills) +- [📚 Browse 1,326+ Skills](#browse-1326-skills) - [🤝 Contributing](#contributing) - [💬 Community](#community) - [☕ Support the Project](#support-the-project) @@ -179,7 +179,7 @@ This installs the same repository-backed skill library through Claude Code's plu ## Best Skills By Tool -If you want a faster answer than "browse all 1,311+ skills", start with a tool-specific guide: +If you want a faster answer than "browse all 1,326+ skills", start with a tool-specific guide: - **[Claude Code skills](docs/users/claude-code-skills.md)**: install paths, starter skills, prompt examples, and plugin marketplace flow. - **[Cursor skills](docs/users/cursor-skills.md)**: best starter skills for `.cursor/skills/`, UI-heavy work, and pair-programming flows. @@ -341,7 +341,7 @@ The repository is organized into specialized domains to transform your AI into a Counts change as new skills are added. For the current full registry, see [CATALOG.md](CATALOG.md). -## Browse 1,311+ Skills +## Browse 1,326+ Skills - Open the interactive browser in [`apps/web-app`](apps/web-app). - Read the full catalog in [`CATALOG.md`](CATALOG.md). @@ -502,6 +502,7 @@ This collection would not be possible without the incredible work of the Claude - **[diet103/claude-code-infrastructure-showcase](https://github.com/diet103/claude-code-infrastructure-showcase)**: Infrastructure and Backend/Frontend Guidelines. - **[ChrisWiles/claude-code-showcase](https://github.com/ChrisWiles/claude-code-showcase)**: React UI patterns and Design Systems. - **[travisvn/awesome-claude-skills](https://github.com/travisvn/awesome-claude-skills)**: Loki Mode and Playwright integration. +- **[Dimillian/Skills](https://github.com/Dimillian/Skills)**: Curated Codex skills focused on Apple platforms, GitHub workflows, refactoring, and performance. Source for `app-store-changelog`, `github`, `ios-debugger-agent`, `macos-menubar-tuist-app`, `macos-spm-app-packaging`, `orchestrate-batch-refactor`, `project-skill-audit`, `react-component-performance`, `simplify-code`, `swift-concurrency-expert`, `swiftui-liquid-glass`, `swiftui-performance-audit`, `swiftui-ui-patterns`, and `swiftui-view-refactor` (MIT). - **[zebbern/claude-code-guide](https://github.com/zebbern/claude-code-guide)**: Comprehensive Security suite & Guide (Source for ~60 new skills). - **[alirezarezvani/claude-skills](https://github.com/alirezarezvani/claude-skills)**: Senior Engineering and PM toolkit. - **[karanb192/awesome-claude-skills](https://github.com/karanb192/awesome-claude-skills)**: A massive list of verified skills for Claude Code. diff --git a/data/bundles.json b/data/bundles.json index 45c34822..e892b43f 100644 --- a/data/bundles.json +++ b/data/bundles.json @@ -173,6 +173,7 @@ "game-development/mobile-games", "gemini-api-dev", "gemini-api-integration", + "github", "go-concurrency-patterns", "go-playwright", "go-rod-master", @@ -183,6 +184,7 @@ "hugging-face-evaluation", "hugging-face-tool-builder", "instagram", + "ios-debugger-agent", "ios-developer", "java-pro", "javascript-mastery", @@ -243,6 +245,7 @@ "python-pro", "python-testing-patterns", "react-best-practices", + "react-component-performance", "react-flow-architect", "react-flow-node-ts", "react-modernization", @@ -267,6 +270,7 @@ "seo-technical", "shopify-apps", "shopify-development", + "snowflake-development", "spline-3d-integration", "sred-work-summary", "stability-ai", @@ -416,8 +420,10 @@ "semgrep-rule-creator", "seo-technical", "service-mesh-expert", + "simplify-code", "skill-scanner", "smtp-penetration-testing", + "snowflake-development", "solidity-security", "spec-to-code-compliance", "sql-injection-testing", @@ -608,12 +614,14 @@ "seo-schema", "seo-technical", "skin-health-analyzer", + "snowflake-development", "spark-optimization", "sql-injection-testing", "sql-optimization-patterns", "sql-pro", "sqlmap-database-pentesting", "supabase-automation", + "swiftui-view-refactor", "tanstack-query-expert", "uniprot-database", "unity-ecs-patterns", @@ -713,6 +721,7 @@ "service-mesh-observability", "skill-installer", "slo-implementation", + "snowflake-development", "temporal-python-pro", "unity-developer", "vercel-deployment", @@ -877,6 +886,7 @@ "stripe-automation", "stripe-integration", "supabase-automation", + "swiftui-ui-patterns", "tdd-orchestrator", "tdd-workflow", "tdd-workflows-tdd-green", @@ -1127,7 +1137,9 @@ "frontend-mobile-development-component-scaffold", "frontend-mobile-security-xss-scan", "game-development/mobile-games", + "ios-debugger-agent", "ios-developer", + "macos-menubar-tuist-app", "makepad-deployment", "makepad-platform", "mobile-design", @@ -1140,6 +1152,10 @@ "seo-technical", "stitch-ui-design", "swiftui-expert-skill", + "swiftui-liquid-glass", + "swiftui-performance-audit", + "swiftui-ui-patterns", + "swiftui-view-refactor", "ui-ux-pro-max", "upgrading-expo" ] diff --git a/data/catalog.json b/data/catalog.json index e123baa9..e1784193 100644 --- a/data/catalog.json +++ b/data/catalog.json @@ -1,6 +1,6 @@ { "generatedAt": "2026-02-08T00:00:00.000Z", - "total": 1311, + "total": 1326, "skills": [ { "id": "00-andruia-consultant", @@ -2239,6 +2239,32 @@ ], "path": "skills/app-builder/templates/SKILL.md" }, + { + "id": "app-store-changelog", + "name": "app-store-changelog", + "description": "Generate user-facing App Store release notes from git history since the last tag.", + "category": "general", + "tags": [ + "app", + "store", + "changelog" + ], + "triggers": [ + "app", + "store", + "changelog", + "generate", + "user", + "facing", + "release", + "notes", + "git", + "history", + "since", + "last" + ], + "path": "skills/app-store-changelog/SKILL.md" + }, { "id": "app-store-optimization", "name": "app-store-optimization", @@ -14902,6 +14928,28 @@ ], "path": "skills/git-pushing/SKILL.md" }, + { + "id": "github", + "name": "github", + "description": "Use the `gh` CLI for issues, pull requests, Actions runs, and GitHub API queries.", + "category": "development", + "tags": [ + "github" + ], + "triggers": [ + "github", + "gh", + "cli", + "issues", + "pull", + "requests", + "actions", + "runs", + "api", + "queries" + ], + "path": "skills/github/SKILL.md" + }, { "id": "github-actions-templates", "name": "github-actions-templates", @@ -16939,6 +16987,28 @@ ], "path": "skills/inventory-demand-planning/SKILL.md" }, + { + "id": "ios-debugger-agent", + "name": "ios-debugger-agent", + "description": "Debug the current iOS project on a booted simulator with XcodeBuildMCP.", + "category": "development", + "tags": [ + "ios", + "debugger", + "agent" + ], + "triggers": [ + "ios", + "debugger", + "agent", + "debug", + "current", + "booted", + "simulator", + "xcodebuildmcp" + ], + "path": "skills/ios-debugger-agent/SKILL.md" + }, { "id": "ios-developer", "name": "ios-developer", @@ -18800,6 +18870,55 @@ ], "path": "skills/machine-learning-ops-ml-pipeline/SKILL.md" }, + { + "id": "macos-menubar-tuist-app", + "name": "macos-menubar-tuist-app", + "description": "Build, refactor, or review SwiftUI macOS menubar apps that use Tuist.", + "category": "general", + "tags": [ + "macos", + "menubar", + "tuist", + "app" + ], + "triggers": [ + "macos", + "menubar", + "tuist", + "app", + "refactor", + "review", + "swiftui", + "apps" + ], + "path": "skills/macos-menubar-tuist-app/SKILL.md" + }, + { + "id": "macos-spm-app-packaging", + "name": "macos-spm-app-packaging", + "description": "Scaffold, build, sign, and package SwiftPM macOS apps without Xcode projects.", + "category": "general", + "tags": [ + "macos", + "spm", + "app", + "packaging" + ], + "triggers": [ + "macos", + "spm", + "app", + "packaging", + "scaffold", + "sign", + "package", + "swiftpm", + "apps", + "without", + "xcode" + ], + "path": "skills/macos-spm-app-packaging/SKILL.md" + }, { "id": "magic-animator", "name": "magic-animator", @@ -21937,6 +22056,32 @@ ], "path": "skills/oral-health-analyzer/SKILL.md" }, + { + "id": "orchestrate-batch-refactor", + "name": "orchestrate-batch-refactor", + "description": "Plan and execute large refactors with dependency-aware work packets and parallel analysis.", + "category": "general", + "tags": [ + "orchestrate", + "batch", + "refactor" + ], + "triggers": [ + "orchestrate", + "batch", + "refactor", + "plan", + "execute", + "large", + "refactors", + "dependency", + "aware", + "work", + "packets", + "parallel" + ], + "path": "skills/orchestrate-batch-refactor/SKILL.md" + }, { "id": "os-scripting", "name": "os-scripting", @@ -23402,6 +23547,27 @@ ], "path": "skills/project-development/SKILL.md" }, + { + "id": "project-skill-audit", + "name": "project-skill-audit", + "description": "Audit a project and recommend the highest-value skills to add or update.", + "category": "general", + "tags": [ + "skill", + "audit" + ], + "triggers": [ + "skill", + "audit", + "recommend", + "highest", + "value", + "skills", + "add", + "update" + ], + "path": "skills/project-skill-audit/SKILL.md" + }, { "id": "projection-patterns", "name": "projection-patterns", @@ -24034,6 +24200,29 @@ ], "path": "skills/react-best-practices/SKILL.md" }, + { + "id": "react-component-performance", + "name": "react-component-performance", + "description": "Diagnose slow React components and suggest targeted performance fixes.", + "category": "development", + "tags": [ + "react", + "component", + "performance" + ], + "triggers": [ + "react", + "component", + "performance", + "diagnose", + "slow", + "components", + "suggest", + "targeted", + "fixes" + ], + "path": "skills/react-component-performance/SKILL.md" + }, { "id": "react-flow-architect", "name": "react-flow-architect", @@ -26912,6 +27101,31 @@ ], "path": "skills/similarity-search-patterns/SKILL.md" }, + { + "id": "simplify-code", + "name": "simplify-code", + "description": "Review a diff for clarity and safe simplifications, then optionally apply low-risk fixes.", + "category": "security", + "tags": [ + "simplify", + "code" + ], + "triggers": [ + "simplify", + "code", + "review", + "diff", + "clarity", + "safe", + "simplifications", + "then", + "optionally", + "apply", + "low", + "risk" + ], + "path": "skills/simplify-code/SKILL.md" + }, { "id": "site-architecture", "name": "site-architecture", @@ -27404,6 +27618,30 @@ ], "path": "skills/smtp-penetration-testing/SKILL.md" }, + { + "id": "snowflake-development", + "name": "snowflake-development", + "description": "Comprehensive Snowflake development assistant covering SQL best practices, data pipeline design (Dynamic Tables, Streams, Tasks, Snowpipe), Cortex AI functions, Cortex Agents, Snowpark Python, dbt integration, performance tuning, and security hardening.", + "category": "security", + "tags": [ + "snowflake" + ], + "triggers": [ + "snowflake", + "development", + "assistant", + "covering", + "sql", + "data", + "pipeline", + "dynamic", + "tables", + "streams", + "tasks", + "snowpipe" + ], + "path": "skills/snowflake-development/SKILL.md" + }, { "id": "social-content", "name": "social-content", @@ -28285,6 +28523,29 @@ ], "path": "skills/sveltekit/SKILL.md" }, + { + "id": "swift-concurrency-expert", + "name": "swift-concurrency-expert", + "description": "Review and fix Swift concurrency issues such as actor isolation and Sendable violations.", + "category": "general", + "tags": [ + "swift", + "concurrency" + ], + "triggers": [ + "swift", + "concurrency", + "review", + "fix", + "issues", + "such", + "actor", + "isolation", + "sendable", + "violations" + ], + "path": "skills/swift-concurrency-expert/SKILL.md" + }, { "id": "swiftui-expert-skill", "name": "swiftui-expert-skill", @@ -28310,6 +28571,98 @@ ], "path": "skills/swiftui-expert-skill/SKILL.md" }, + { + "id": "swiftui-liquid-glass", + "name": "swiftui-liquid-glass", + "description": "Implement or review SwiftUI Liquid Glass APIs with correct fallbacks and modifier order.", + "category": "general", + "tags": [ + "swiftui", + "liquid", + "glass" + ], + "triggers": [ + "swiftui", + "liquid", + "glass", + "review", + "apis", + "correct", + "fallbacks", + "modifier", + "order" + ], + "path": "skills/swiftui-liquid-glass/SKILL.md" + }, + { + "id": "swiftui-performance-audit", + "name": "swiftui-performance-audit", + "description": "Audit SwiftUI performance issues from code review and profiling evidence.", + "category": "general", + "tags": [ + "swiftui", + "performance", + "audit" + ], + "triggers": [ + "swiftui", + "performance", + "audit", + "issues", + "code", + "review", + "profiling", + "evidence" + ], + "path": "skills/swiftui-performance-audit/SKILL.md" + }, + { + "id": "swiftui-ui-patterns", + "name": "swiftui-ui-patterns", + "description": "Apply proven SwiftUI UI patterns for navigation, sheets, async state, and reusable screens.", + "category": "architecture", + "tags": [ + "swiftui", + "ui" + ], + "triggers": [ + "swiftui", + "ui", + "apply", + "proven", + "navigation", + "sheets", + "async", + "state", + "reusable", + "screens" + ], + "path": "skills/swiftui-ui-patterns/SKILL.md" + }, + { + "id": "swiftui-view-refactor", + "name": "swiftui-view-refactor", + "description": "Refactor SwiftUI views into smaller components with stable, explicit data flow.", + "category": "data-ai", + "tags": [ + "swiftui", + "view", + "refactor" + ], + "triggers": [ + "swiftui", + "view", + "refactor", + "views", + "smaller", + "components", + "stable", + "explicit", + "data", + "flow" + ], + "path": "skills/swiftui-view-refactor/SKILL.md" + }, { "id": "sympy", "name": "sympy", diff --git a/docs/integrations/jetski-cortex.md b/docs/integrations/jetski-cortex.md index c78a8ca5..9fc61df5 100644 --- a/docs/integrations/jetski-cortex.md +++ b/docs/integrations/jetski-cortex.md @@ -1,9 +1,9 @@ --- title: Jetski/Cortex + Gemini Integration Guide -description: "Come usare antigravity-awesome-skills con Jetski/Cortex evitando l’overflow di contesto con 1.311+ skill." +description: "Come usare antigravity-awesome-skills con Jetski/Cortex evitando l’overflow di contesto con 1.326+ skill." --- -# Jetski/Cortex + Gemini: integrazione sicura con 1.311+ skill +# Jetski/Cortex + Gemini: integrazione sicura con 1.326+ skill Questa guida mostra come integrare il repository `antigravity-awesome-skills` con un agente basato su **Jetski/Cortex + Gemini** (o framework simili) **senza superare il context window** del modello. @@ -23,7 +23,7 @@ Non bisogna mai: - concatenare il contenuto di tutte le `SKILL.md` in un singolo system prompt; - reiniettare l’intera libreria per **ogni** richiesta. -Con oltre 1.311 skill, questo approccio riempie il context window prima ancora di aggiungere i messaggi dell’utente, causando l’errore di truncation. +Con oltre 1.326 skill, questo approccio riempie il context window prima ancora di aggiungere i messaggi dell’utente, causando l’errore di truncation. --- diff --git a/docs/integrations/jetski-gemini-loader/README.md b/docs/integrations/jetski-gemini-loader/README.md index f81fd7c0..d249df61 100644 --- a/docs/integrations/jetski-gemini-loader/README.md +++ b/docs/integrations/jetski-gemini-loader/README.md @@ -20,7 +20,7 @@ This example shows one way to integrate **antigravity-awesome-skills** with a Je - How to enforce a **maximum number of skills per turn** via `maxSkillsPerTurn`. - How to choose whether to **truncate or error** when too many skills are requested via `overflowBehavior`. -This pattern avoids context overflow when you have 1,311+ skills installed. +This pattern avoids context overflow when you have 1,326+ skills installed. --- diff --git a/docs/maintainers/repo-growth-seo.md b/docs/maintainers/repo-growth-seo.md index 08329782..a75d57d4 100644 --- a/docs/maintainers/repo-growth-seo.md +++ b/docs/maintainers/repo-growth-seo.md @@ -6,7 +6,7 @@ This document keeps the repository's GitHub-facing discovery copy aligned with t Preferred positioning: -> Installable GitHub library of 1,311+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and other AI coding assistants. +> Installable GitHub library of 1,326+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and other AI coding assistants. Key framing: @@ -20,7 +20,7 @@ Key framing: Preferred description: -> Installable GitHub library of 1,311+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and more. Includes installer CLI, bundles, workflows, and official/community skill collections. +> Installable GitHub library of 1,326+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and more. Includes installer CLI, bundles, workflows, and official/community skill collections. Preferred homepage: @@ -28,7 +28,7 @@ Preferred homepage: Preferred social preview: -- use a clean preview image that says `1,311+ Agentic Skills`; +- use a clean preview image that says `1,326+ Agentic Skills`; - mention Claude Code, Cursor, Codex CLI, and Gemini CLI; - avoid dense text and tiny logos that disappear in social cards. diff --git a/docs/maintainers/skills-update-guide.md b/docs/maintainers/skills-update-guide.md index 448cba0f..e728b9ad 100644 --- a/docs/maintainers/skills-update-guide.md +++ b/docs/maintainers/skills-update-guide.md @@ -69,7 +69,7 @@ For manual updates, you need: The update process refreshes: - Skills index (`skills_index.json`) - Web app skills data (`apps\web-app\public\skills.json`) -- All 1,311+ skills from the skills directory +- All 1,326+ skills from the skills directory ## When to Update diff --git a/docs/sources/sources.md b/docs/sources/sources.md index f1a72b6e..451ee808 100644 --- a/docs/sources/sources.md +++ b/docs/sources/sources.md @@ -80,6 +80,16 @@ The following skills were added from the curated collection at [VoltAgent/awesom | `fp-ts-react` | [whatiskadudoing/fp-ts-skills](https://github.com/whatiskadudoing/fp-ts-skills) | Compatible | fp-ts with React 18/19 and Next.js | | `fp-ts-errors` | [whatiskadudoing/fp-ts-skills](https://github.com/whatiskadudoing/fp-ts-skills) | Compatible | Type-safe error handling with Either and TaskEither | +## Skills from Dimillian/Skills + +The following skills were added from [Dimillian/Skills](https://github.com/Dimillian/Skills), a curated Codex skills repository focused on Apple platforms, GitHub workflows, refactoring, and performance work. + +| Skill / Group | Original Source | License | Notes | +| :------------ | :-------------- | :------ | :---- | +| `app-store-changelog`, `github`, `orchestrate-batch-refactor`, `project-skill-audit`, `react-component-performance`, `simplify-code` | [Dimillian/Skills](https://github.com/Dimillian/Skills) | MIT | Workflow, GitHub, React performance, and code simplification skills. | +| `ios-debugger-agent`, `macos-menubar-tuist-app`, `macos-spm-app-packaging`, `swift-concurrency-expert` | [Dimillian/Skills](https://github.com/Dimillian/Skills) | MIT | Apple-platform debugging, packaging, and Swift concurrency guidance. | +| `swiftui-liquid-glass`, `swiftui-performance-audit`, `swiftui-ui-patterns`, `swiftui-view-refactor` | [Dimillian/Skills](https://github.com/Dimillian/Skills) | MIT | SwiftUI-specific implementation, performance, and refactoring patterns. | + --- ## Recently Added Skills (March 2026) diff --git a/docs/users/bundles.md b/docs/users/bundles.md index a5e68952..aefc91c2 100644 --- a/docs/users/bundles.md +++ b/docs/users/bundles.md @@ -4,6 +4,8 @@ > These packs are curated starter recommendations for humans. Generated bundle ids in `data/bundles.json` are broader catalog/workflow groupings and do not need to map 1:1 to the editorial packs below. +> **Important:** bundles are not invokable mega-skills such as `@web-wizard` or `/essentials-bundle`. Use the individual skills listed in the pack, or use the activation scripts if you want only that bundle's skills active in your live Antigravity directory. + ## Quick Start 1. **Install the repository:** @@ -22,6 +24,12 @@ - Gemini CLI: `Use skill-name...` - Codex CLI: `Use skill-name...` +If you want a bundle to behave like a focused active subset instead of a reading list, use: + +- macOS/Linux: `./scripts/activate-skills.sh --clear Essentials` +- macOS/Linux: `./scripts/activate-skills.sh --clear "Web Wizard"` +- Windows: `.\scripts\activate-skills.bat --clear Essentials` + --- ## Essentials & Core @@ -579,4 +587,4 @@ Found a skill that should be in a bundle? Or want to create a new bundle? [Open --- -_Last updated: March 2026 | Total Skills: 1,311+ | Total Bundles: 36_ +_Last updated: March 2026 | Total Skills: 1,326+ | Total Bundles: 36_ diff --git a/docs/users/claude-code-skills.md b/docs/users/claude-code-skills.md index 985d4628..42d2a5ca 100644 --- a/docs/users/claude-code-skills.md +++ b/docs/users/claude-code-skills.md @@ -6,7 +6,7 @@ Antigravity Awesome Skills gives Claude Code users an installable library of `SK ## Why use this repo for Claude Code -- It includes 1,311+ skills instead of a narrow single-domain starter pack. +- It includes 1,326+ skills instead of a narrow single-domain starter pack. - It supports the standard `.claude/skills/` path and the Claude Code plugin marketplace flow. - It includes onboarding docs, bundles, and workflows so new users do not need to guess where to begin. - It covers both everyday engineering tasks and specialized work like security reviews, infrastructure, product planning, and documentation. diff --git a/docs/users/faq.md b/docs/users/faq.md index 101f5beb..b6dda789 100644 --- a/docs/users/faq.md +++ b/docs/users/faq.md @@ -162,6 +162,26 @@ If Antigravity becomes unstable only when the full skills library is active, swi That guide shows how to run `scripts/activate-skills.sh` from a cloned copy of this repository so only the bundles or skill ids you need stay active in `~/.gemini/antigravity/skills`. +### Gemini CLI hangs after a few turns or says "This is taking a bit longer, we're still on it". What should I do? + +Start with a quick isolation check: + +1. Start a brand-new Gemini CLI conversation. +2. Try one prompt with no skills at all. +3. Try the same task again with only one small skill such as `brainstorming`. +4. Temporarily reduce your active skill set to 2-5 skills and retry. + +How to interpret the result: + +- If plain Gemini CLI hangs even without skills, the problem is likely on the Gemini CLI/runtime side rather than this repository. +- If plain Gemini works, but hangs only when skills are present or after several turns, the likely cause is conversation/context growth. + +In that case: + +- keep a much smaller active set +- start fresh conversations more often +- use the overload guide: [agent-overload-recovery.md](agent-overload-recovery.md) + ### How do I update skills? Navigate to your skills directory and pull the latest changes: @@ -185,6 +205,22 @@ Use the `@` symbol followed by the skill name: @brainstorming help me design a todo app ``` +### Can I invoke a whole bundle like `@Essentials` or `/web-wizard`? + +No. Bundles are curated lists of skills, not standalone invokable mega-skills. + +Use them in one of these two ways: + +- pick individual skills from the bundle and invoke those directly +- use the activation scripts if you want only that bundle's skills active in Antigravity + +Examples: + +```bash +./scripts/activate-skills.sh --clear Essentials +./scripts/activate-skills.sh --clear "Web Wizard" +``` + ### Can I use multiple skills at once? **Yes!** You can invoke multiple skills: diff --git a/docs/users/gemini-cli-skills.md b/docs/users/gemini-cli-skills.md index 7d47d2be..fc7ecbad 100644 --- a/docs/users/gemini-cli-skills.md +++ b/docs/users/gemini-cli-skills.md @@ -8,7 +8,7 @@ Antigravity Awesome Skills supports Gemini CLI through the `.gemini/skills/` pat - It installs directly into the expected Gemini skills path. - It includes both core software engineering skills and deeper agent/LLM-oriented skills. -- It helps new users get started with bundles and workflows rather than forcing a cold start from 1,311+ files. +- It helps new users get started with bundles and workflows rather than forcing a cold start from 1,326+ files. - It is useful whether you want a broad internal skill library or a single repo to test many workflows quickly. ## Install Gemini CLI Skills diff --git a/docs/users/kiro-integration.md b/docs/users/kiro-integration.md index 59ad82b7..87b11376 100644 --- a/docs/users/kiro-integration.md +++ b/docs/users/kiro-integration.md @@ -18,7 +18,7 @@ Kiro is AWS's agentic AI IDE that combines: Kiro's agentic capabilities are enhanced by skills that provide: -- **Domain expertise** across 1,311+ specialized areas +- **Domain expertise** across 1,326+ specialized areas - **Best practices** from Anthropic, OpenAI, Google, Microsoft, and AWS - **Workflow automation** for common development tasks - **AWS-specific patterns** for serverless, infrastructure, and cloud architecture diff --git a/docs/users/usage.md b/docs/users/usage.md index e2309cff..dd19c1d7 100644 --- a/docs/users/usage.md +++ b/docs/users/usage.md @@ -12,7 +12,7 @@ Great question! Here's what just happened and what to do next: When you ran `npx antigravity-awesome-skills` or cloned the repository, you: -✅ **Downloaded 1,311+ skill files** to your computer (default: `~/.gemini/antigravity/skills/`; or a custom path like `~/.agent/skills/` if you used `--path`) +✅ **Downloaded 1,326+ skill files** to your computer (default: `~/.gemini/antigravity/skills/`; or a custom path like `~/.agent/skills/` if you used `--path`) ✅ **Made them available** to your AI assistant ❌ **Did NOT enable them all automatically** (they're just sitting there, waiting) @@ -32,7 +32,7 @@ Bundles are **recommended lists** of skills grouped by role. They help you decid **Analogy:** -- You installed a toolbox with 1,311+ tools (✅ done) +- You installed a toolbox with 1,326+ tools (✅ done) - Bundles are like **labeled organizer trays** saying: "If you're a carpenter, start with these 10 tools" - You don't install bundles—you **pick skills from them** @@ -41,6 +41,7 @@ Bundles are **recommended lists** of skills grouped by role. They help you decid ❌ Separate installations ❌ Different download commands ❌ Something most users need to activate during normal install +❌ Invokable mega-skills like `@essentials` or `/web-wizard` ### Example: The "Web Wizard" Bundle @@ -53,6 +54,13 @@ When you see the [Web Wizard bundle](bundles.md#-the-web-wizard-pack), it lists: These are **recommendations** for which skills a web developer should try first. They're already installed—you just need to **use them in your prompts**. +If you want only one bundle active at a time in Antigravity, use the activation scripts instead of trying to invoke the bundle name directly: + +```bash +./scripts/activate-skills.sh --clear Essentials +./scripts/activate-skills.sh --clear "Web Wizard" +``` + --- ## Step 2: How to Actually Execute/Use a Skill @@ -88,6 +96,8 @@ The exact syntax varies by tool, but it's always simple: Use the brainstorming skill to help me plan my app ``` +If Gemini CLI starts hanging after a few turns, try a fresh conversation and temporarily reduce the active set to just 2-5 skills to rule out context growth. + #### Codex CLI ```bash @@ -192,7 +202,7 @@ Let's actually use a skill right now. Follow these steps: ## Step 5: Picking Your First Skills (Practical Advice) -Don't try to use all 1,311+ skills at once. Here's a sensible approach: +Don't try to use all 1,326+ skills at once. Here's a sensible approach: If you want a tool-specific starting point before choosing skills, use: @@ -323,7 +333,7 @@ Usually no, but if your AI doesn't recognize a skill: ### "Can I load all skills into the model at once?" -No. Even though you have 1,311+ skills installed locally, you should **not** concatenate every `SKILL.md` into a single system prompt or context block. +No. Even though you have 1,326+ skills installed locally, you should **not** concatenate every `SKILL.md` into a single system prompt or context block. The intended pattern is: diff --git a/docs/users/visual-guide.md b/docs/users/visual-guide.md index f188047d..6e1df66c 100644 --- a/docs/users/visual-guide.md +++ b/docs/users/visual-guide.md @@ -34,7 +34,7 @@ antigravity-awesome-skills/ ├── 📄 CONTRIBUTING.md ← Contributor workflow ├── 📄 CATALOG.md ← Full generated catalog │ -├── 📁 skills/ ← 1,311+ skills live here +├── 📁 skills/ ← 1,326+ skills live here │ │ │ ├── 📁 brainstorming/ │ │ └── 📄 SKILL.md ← Skill definition @@ -47,7 +47,7 @@ antigravity-awesome-skills/ │ │ └── 📁 2d-games/ │ │ └── 📄 SKILL.md ← Nested skills also supported │ │ -│ └── ... (1,311+ total) +│ └── ... (1,326+ total) │ ├── 📁 apps/ │ └── 📁 web-app/ ← Interactive browser @@ -100,7 +100,7 @@ antigravity-awesome-skills/ ``` ┌─────────────────────────┐ - │ 1,311+ SKILLS │ + │ 1,326+ SKILLS │ └────────────┬────────────┘ │ ┌────────────────────────┼────────────────────────┐ @@ -201,7 +201,7 @@ If you want a workspace-style manual install instead, cloning into `.agent/skill │ ├── 📁 brainstorming/ │ │ ├── 📁 stripe-integration/ │ │ ├── 📁 react-best-practices/ │ -│ └── ... (1,311+ total) │ +│ └── ... (1,326+ total) │ └─────────────────────────────────────────┘ ``` diff --git a/package.json b/package.json index 1815fc7f..158410ea 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "antigravity-awesome-skills", "version": "8.8.0", - "description": "1,311+ agentic skills for Claude Code, Gemini CLI, Cursor, Antigravity & more. Installer CLI.", + "description": "1,326+ agentic skills for Claude Code, Gemini CLI, Cursor, Antigravity & more. Installer CLI.", "license": "MIT", "scripts": { "validate": "node tools/scripts/run-python.js tools/scripts/validate_skills.py", diff --git a/skills/app-store-changelog/SKILL.md b/skills/app-store-changelog/SKILL.md new file mode 100644 index 00000000..a8f783ca --- /dev/null +++ b/skills/app-store-changelog/SKILL.md @@ -0,0 +1,75 @@ +--- +name: app-store-changelog +description: Generate user-facing App Store release notes from git history since the last tag. +risk: safe +source: "Dimillian/Skills (MIT)" +date_added: "2026-03-25" +--- + +# App Store Changelog + +## Overview +Generate a comprehensive, user-facing changelog from git history since the last tag, then translate commits into clear App Store release notes. + +## When to Use + +- When the user asks for App Store "What's New" text or release notes from git history. +- When you need to turn raw commits into concise, user-facing release bullets. + +## Workflow + +### 1) Collect changes +- Run `scripts/collect_release_changes.sh` from the repo root to gather commits and touched files. +- If needed, pass a specific tag or ref: `scripts/collect_release_changes.sh v1.2.3 HEAD`. +- If no tags exist, the script falls back to full history. + +### 2) Triage for user impact +- Scan commits and files to identify user-visible changes. +- Group changes by theme (New, Improved, Fixed) and deduplicate overlaps. +- Drop internal-only work (build scripts, refactors, dependency bumps, CI). + +### 3) Draft App Store notes +- Write short, benefit-focused bullets for each user-facing change. +- Use clear verbs and plain language; avoid internal jargon. +- Prefer 5 to 10 bullets unless the user requests a different length. + +### 4) Validate +- Ensure every bullet maps back to a real change in the range. +- Check for duplicates and overly technical wording. +- Ask for clarification if any change is ambiguous or possibly internal-only. + +## Commit-to-Bullet Examples + +The following shows how raw commits are translated into App Store bullets: + +| Raw commit message | App Store bullet | +|---|---| +| `fix(auth): resolve token refresh race condition on iOS 17` | • Fixed a login issue that could leave some users unexpectedly signed out. | +| `feat(search): add voice input to search bar` | • Search your library hands-free with the new voice input option. | +| `perf(timeline): lazy-load images to reduce scroll jank` | • Scrolling through your timeline is now smoother and faster. | + +Internal-only commits that are **dropped** (no user impact): +- `chore: upgrade fastlane to 2.219` +- `refactor(network): extract URLSession wrapper into module` +- `ci: add nightly build job` + +## Example Output + +``` +What's New in Version 3.4 + +• Search your library hands-free with the new voice input option. +• Scrolling through your timeline is now smoother and faster. +• Fixed a login issue that could leave some users unexpectedly signed out. +• Added dark-mode support to the settings screen. +• Improved load times when opening large photo albums. +``` + +## Output Format +- Title (optional): "What's New" or product name + version. +- Bullet list only; one sentence per bullet. +- Stick to storefront limits if the user provides one. + +## Resources +- `scripts/collect_release_changes.sh`: Collect commits and touched files since last tag. +- `references/release-notes-guidelines.md`: Language, filtering, and QA rules for App Store notes. diff --git a/skills/app-store-changelog/agents/openai.yaml b/skills/app-store-changelog/agents/openai.yaml new file mode 100644 index 00000000..bec0a429 --- /dev/null +++ b/skills/app-store-changelog/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "App Store Changelog" + short_description: "Generate App Store release notes" + default_prompt: "Use $app-store-changelog to draft App Store release notes from the changes since the last tag." diff --git a/skills/app-store-changelog/references/release-notes-guidelines.md b/skills/app-store-changelog/references/release-notes-guidelines.md new file mode 100644 index 00000000..c0beecb1 --- /dev/null +++ b/skills/app-store-changelog/references/release-notes-guidelines.md @@ -0,0 +1,34 @@ +# App Store Release Notes Guidelines + +## Goals +- Produce user-facing release notes that describe visible changes since the last tag. +- Include all user-impacting changes; omit purely internal or refactor-only work. +- Keep language plain, short, and benefit-focused. + +## Output Shape +- Prefer 5 to 10 bullets total for most releases. +- Group by theme if needed: New, Improved, Fixed. +- Each bullet should be one sentence and start with a verb. +- Avoid internal codenames, ticket IDs, or file paths. + +## Filtering Rules +- Include: new features, UI changes, behavior changes, bug fixes users would notice, performance improvements with visible impact. +- Exclude: refactors, dependency bumps, CI changes, developer tooling, internal logging, analytics changes unless they affect user privacy or behavior. +- If a change is ambiguous, ask for clarification or describe it as a small improvement only if it is user-visible. + +## Language Guidance +- Translate technical terms into user-facing descriptions. +- Avoid versions of "API", "refactor", "nil", "crash log", or "dependency". +- Prefer "Improved", "Added", "Fixed", "Updated" or action verbs like "Search", "Upload", "Sync". +- Keep tense present or past: "Added", "Improved", "Fixed". + +## Examples +- "Added account switching from the profile menu." +- "Improved timeline loading speed on slow connections." +- "Fixed media attachments not opening in full screen." + +## QA Checklist +- Every bullet ties to a real change in the range. +- No duplicate bullets that describe the same change. +- No internal jargon or file paths. +- Final list fits App Store text limits for the target storefront if provided. diff --git a/skills/app-store-changelog/scripts/collect_release_changes.sh b/skills/app-store-changelog/scripts/collect_release_changes.sh new file mode 100755 index 00000000..f7e4659a --- /dev/null +++ b/skills/app-store-changelog/scripts/collect_release_changes.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +since_ref="${1:-}" +until_ref="${2:-HEAD}" + +if [[ -z "${since_ref}" ]]; then + if git describe --tags --abbrev=0 >/dev/null 2>&1; then + since_ref="$(git describe --tags --abbrev=0)" + fi +fi + +range="" +if [[ -n "${since_ref}" ]]; then + range="${since_ref}..${until_ref}" +else + range="${until_ref}" +fi + +repo_root="$(git rev-parse --show-toplevel)" + +printf "Repo: %s\n" "${repo_root}" +if [[ -n "${since_ref}" ]]; then + printf "Range: %s..%s\n" "${since_ref}" "${until_ref}" +else + printf "Range: start..%s (no tags found)\n" "${until_ref}" +fi + +printf "\n== Commits ==\n" +git log --reverse --date=short --pretty=format:'%h|%ad|%s' ${range} + +printf "\n\n== Files Touched ==\n" +git log --reverse --name-only --pretty=format:'--- %h %s' ${range} | sed '/^$/d' diff --git a/skills/github/SKILL.md b/skills/github/SKILL.md new file mode 100644 index 00000000..c767e048 --- /dev/null +++ b/skills/github/SKILL.md @@ -0,0 +1,76 @@ +--- +name: github +description: "Use the `gh` CLI for issues, pull requests, Actions runs, and GitHub API queries." +risk: safe +source: "Dimillian/Skills (MIT)" +date_added: "2026-03-25" +--- + +# GitHub Skill + +Use the `gh` CLI to interact with GitHub. Always specify `--repo owner/repo` when not in a git directory, or use URLs directly. + +## When to Use + +- When the user asks about GitHub issues, pull requests, workflow runs, or CI failures. +- When you need `gh issue`, `gh pr`, `gh run`, or `gh api` from the command line. + +## Pull Requests + +Check CI status on a PR: +```bash +gh pr checks 55 --repo owner/repo +``` + +List recent workflow runs: +```bash +gh run list --repo owner/repo --limit 10 +``` + +View a run and see which steps failed: +```bash +gh run view --repo owner/repo +``` + +View logs for failed steps only: +```bash +gh run view --repo owner/repo --log-failed +``` + +### Debugging a CI Failure + +Follow this sequence to investigate a failing CI run: + +1. **Check PR status** — identify which checks are failing: + ```bash + gh pr checks 55 --repo owner/repo + ``` +2. **List recent runs** — find the relevant run ID: + ```bash + gh run list --repo owner/repo --limit 10 + ``` +3. **View the failed run** — see which jobs and steps failed: + ```bash + gh run view --repo owner/repo + ``` +4. **Fetch failure logs** — get the detailed output for failed steps: + ```bash + gh run view --repo owner/repo --log-failed + ``` + +## API for Advanced Queries + +The `gh api` command is useful for accessing data not available through other subcommands. + +Get PR with specific fields: +```bash +gh api repos/owner/repo/pulls/55 --jq '.title, .state, .user.login' +``` + +## JSON Output + +Most commands support `--json` for structured output. You can use `--jq` to filter: + +```bash +gh issue list --repo owner/repo --json number,title --jq '.[] | "\(.number): \(.title)"' +``` diff --git a/skills/github/agents/openai.yaml b/skills/github/agents/openai.yaml new file mode 100644 index 00000000..294b3318 --- /dev/null +++ b/skills/github/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "GitHub" + short_description: "Use gh for GitHub workflows" + default_prompt: "Use $github to inspect this repository's pull requests, issues, runs, or API data." diff --git a/skills/ios-debugger-agent/SKILL.md b/skills/ios-debugger-agent/SKILL.md new file mode 100644 index 00000000..88b75ff4 --- /dev/null +++ b/skills/ios-debugger-agent/SKILL.md @@ -0,0 +1,59 @@ +--- +name: ios-debugger-agent +description: Debug the current iOS project on a booted simulator with XcodeBuildMCP. +risk: safe +source: "Dimillian/Skills (MIT)" +date_added: "2026-03-25" +--- + +# iOS Debugger Agent + +## Overview +Use XcodeBuildMCP to build and run the current project scheme on a booted iOS simulator, interact with the UI, and capture logs. Prefer the MCP tools for simulator control, logs, and view inspection. + +## When to Use + +- When the user asks to run, debug, or inspect an iOS app on a simulator. +- When you need simulator UI interaction, screenshots, or runtime logs via XcodeBuildMCP. + +## Core Workflow +Follow this sequence unless the user asks for a narrower action. + +### 1) Discover the booted simulator +- Call `mcp__XcodeBuildMCP__list_sims` and select the simulator with state `Booted`. +- If none are booted, ask the user to boot one (do not boot automatically unless asked). + +### 2) Set session defaults +- Call `mcp__XcodeBuildMCP__session-set-defaults` with: + - `projectPath` or `workspacePath` (whichever the repo uses) + - `scheme` for the current app + - `simulatorId` from the booted device + - Optional: `configuration: "Debug"`, `useLatestOS: true` + +### 3) Build + run (when requested) +- Call `mcp__XcodeBuildMCP__build_run_sim`. +- **If the build fails**, check the error output and retry (optionally with `preferXcodebuild: true`) or escalate to the user before attempting any UI interaction. +- **After a successful build**, verify the app launched by calling `mcp__XcodeBuildMCP__describe_ui` or `mcp__XcodeBuildMCP__screenshot` before proceeding to UI interaction. +- If the app is already built and only launch is requested, use `mcp__XcodeBuildMCP__launch_app_sim`. +- If bundle id is unknown: + 1) `mcp__XcodeBuildMCP__get_sim_app_path` + 2) `mcp__XcodeBuildMCP__get_app_bundle_id` + +## UI Interaction & Debugging +Use these when asked to inspect or interact with the running app. + +- **Describe UI**: `mcp__XcodeBuildMCP__describe_ui` before tapping or swiping. +- **Tap**: `mcp__XcodeBuildMCP__tap` (prefer `id` or `label`; use coordinates only if needed). +- **Type**: `mcp__XcodeBuildMCP__type_text` after focusing a field. +- **Gestures**: `mcp__XcodeBuildMCP__gesture` for common scrolls and edge swipes. +- **Screenshot**: `mcp__XcodeBuildMCP__screenshot` for visual confirmation. + +## Logs & Console Output +- Start logs: `mcp__XcodeBuildMCP__start_sim_log_cap` with the app bundle id. +- Stop logs: `mcp__XcodeBuildMCP__stop_sim_log_cap` and summarize important lines. +- For console output, set `captureConsole: true` and relaunch if required. + +## Troubleshooting +- If build fails, ask whether to retry with `preferXcodebuild: true`. +- If the wrong app launches, confirm the scheme and bundle id. +- If UI elements are not hittable, re-run `describe_ui` after layout changes. diff --git a/skills/ios-debugger-agent/agents/openai.yaml b/skills/ios-debugger-agent/agents/openai.yaml new file mode 100644 index 00000000..6caff63f --- /dev/null +++ b/skills/ios-debugger-agent/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "iOS Debugger Agent" + short_description: "Debug iOS apps on Simulator" + default_prompt: "Use $ios-debugger-agent to build, launch, and inspect the current iOS app on the booted simulator." diff --git a/skills/macos-menubar-tuist-app/SKILL.md b/skills/macos-menubar-tuist-app/SKILL.md new file mode 100644 index 00000000..6c6f1339 --- /dev/null +++ b/skills/macos-menubar-tuist-app/SKILL.md @@ -0,0 +1,109 @@ +--- +name: macos-menubar-tuist-app +description: Build, refactor, or review SwiftUI macOS menubar apps that use Tuist. +risk: safe +source: "Dimillian/Skills (MIT)" +date_added: "2026-03-25" +--- + +# macos-menubar-tuist-app + +Build and maintain macOS menubar apps with a Tuist-first workflow and stable launch scripts. Preserve strict architecture boundaries so networking, state, and UI remain testable and predictable. + +## When to Use + +- When working on LSUIElement menubar utilities built with Tuist and SwiftUI. +- When you need Tuist manifests, launch scripts, or architecture guidance for a menubar app. + +## Core Rules + +- Keep the app menubar-only unless explicitly told otherwise. Use `LSUIElement = true` by default. +- Keep transport and decoding logic outside views. Do not call networking from SwiftUI view bodies. +- Keep state transitions in a store layer (`@Observable` or equivalent), not in row/view presentation code. +- Keep model decoding resilient to API drift: optional fields, safe fallbacks, and defensive parsing. +- Treat Tuist manifests as the source of truth. Do not rely on hand-edited generated Xcode artifacts. +- Prefer script-based launch for local iteration when `tuist run` is unreliable for macOS target/device resolution. +- Prefer `tuist xcodebuild build` over raw `xcodebuild` in local run scripts when building generated projects. + +## Expected File Shape + +Use this placement by default: + +- `Project.swift`: app target, settings, resources, `Info.plist` keys +- `Sources/*Model*.swift`: API/domain models and decoding +- `Sources/*Client*.swift`: requests, response mapping, transport concerns +- `Sources/*Store*.swift`: observable state, refresh policy, filtering, caching +- `Sources/*Menu*View*.swift`: menu composition and top-level UI state +- `Sources/*Row*View*.swift`: row rendering and lightweight interactions +- `run-menubar.sh`: canonical local restart/build/launch path +- `stop-menubar.sh`: explicit stop helper when needed + +## Workflow + +1. Confirm Tuist ownership +- Verify `Tuist.swift` and `Project.swift` (or workspace manifests) exist. +- Read existing run scripts before changing launch behavior. + +2. Probe backend behavior before coding assumptions +- Use `curl` to verify endpoint shape, auth requirements, and pagination behavior. +- If endpoint ignores `limit/page`, implement full-list handling with local trimming in the store. + +3. Implement layers from bottom to top +- Define/adjust models first. +- Add or update client request/decoding logic. +- Update store refresh, filtering, and cache policy. +- Wire views last. + +4. Keep app wiring minimal +- Keep app entry focused on scene/menu wiring and dependency injection. +- Avoid embedding business logic in `App` or menu scene declarations. + +5. Standardize launch ergonomics +- Ensure run script restarts an existing instance before relaunching. +- Ensure run script does not open Xcode as a side effect. +- Use `tuist generate --no-open` when generation is required. +- When the run script builds the generated project, prefer `TUIST_SKIP_UPDATE_CHECK=1 tuist xcodebuild build ...` instead of invoking raw `xcodebuild` directly. + +## Validation Matrix + +Run validations after edits: + +```bash +TUIST_SKIP_UPDATE_CHECK=1 tuist xcodebuild build -scheme -configuration Debug +``` + +If launch workflow changed: + +```bash +./run-menubar.sh +``` + +If shell scripts changed: + +```bash +bash -n run-menubar.sh +bash -n stop-menubar.sh +./run-menubar.sh +``` + +## Failure Patterns and Fix Direction + +- `tuist run` cannot resolve the macOS destination: +Use run/stop scripts as canonical local run path. + +- Menu UI is laggy or inconsistent after refresh: +Move derived state and filtering into the store; keep views render-only. + +- API payload changes break decode: +Relax model decoding with optional fields and defaults, then surface missing data safely in UI. + +- Feature asks for quick UI patch: +Trace root cause in model/client/store before changing row/menu presentation. + +## Completion Checklist + +- Preserve menubar-only behavior unless explicitly changed. +- Keep network and state logic out of SwiftUI view bodies. +- Keep Tuist manifests and run scripts aligned with actual build/run flow. +- Run the validation matrix for touched areas. +- Report concrete commands run and outcomes. diff --git a/skills/macos-menubar-tuist-app/agents/openai.yaml b/skills/macos-menubar-tuist-app/agents/openai.yaml new file mode 100644 index 00000000..a235c1b1 --- /dev/null +++ b/skills/macos-menubar-tuist-app/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "macOS Menubar Tuist App" + short_description: "Tuist-first macOS menubar app build and maintenance" + default_prompt: "Use $macos-menubar-tuist-app to scaffold or refactor a menubar-only macOS app with Tuist, layered state architecture, and script-based run validation." diff --git a/skills/macos-spm-app-packaging/SKILL.md b/skills/macos-spm-app-packaging/SKILL.md new file mode 100644 index 00000000..07ddf636 --- /dev/null +++ b/skills/macos-spm-app-packaging/SKILL.md @@ -0,0 +1,105 @@ +--- +name: macos-spm-app-packaging +description: Scaffold, build, sign, and package SwiftPM macOS apps without Xcode projects. +risk: safe +source: "Dimillian/Skills (MIT)" +date_added: "2026-03-25" +--- + +# macOS SwiftPM App Packaging (No Xcode) + +## Overview +Bootstrap a complete SwiftPM macOS app folder, then build, package, and run it without Xcode. Use `assets/templates/bootstrap/` for the starter layout and `references/packaging.md` + `references/release.md` for packaging and release details. + +## When to Use + +- When the user needs a SwiftPM-based macOS app without relying on an Xcode project. +- When you need packaging, signing, notarization, or appcast guidance for a SwiftPM app. + +## Two-Step Workflow +1) Bootstrap the project folder + - Copy `assets/templates/bootstrap/` into a new repo. + - Rename `MyApp` in `Package.swift`, `Sources/MyApp/`, and `version.env`. + - Customize `APP_NAME`, `BUNDLE_ID`, and versions. + +2) Build, package, and run the bootstrapped app + - Copy scripts from `assets/templates/` into your repo (for example, `Scripts/`). + - Build/tests: `swift build` and `swift test`. + - Package: `Scripts/package_app.sh`. + - Run: `Scripts/compile_and_run.sh` (preferred) or `Scripts/launch.sh`. + - Release (optional): `Scripts/sign-and-notarize.sh` and `Scripts/make_appcast.sh`. + - Tag + GitHub release (optional): create a git tag, upload the zip/appcast to the GitHub release, and publish. + +## Minimum End-to-End Example +Shortest path from bootstrap to a running app: +```bash +# 1. Copy and rename the skeleton +cp -R assets/templates/bootstrap/ ~/Projects/MyApp +cd ~/Projects/MyApp +sed -i '' 's/MyApp/HelloApp/g' Package.swift version.env + +# 2. Copy scripts +cp assets/templates/package_app.sh Scripts/ +cp assets/templates/compile_and_run.sh Scripts/ +chmod +x Scripts/*.sh + +# 3. Build and launch +swift build +Scripts/compile_and_run.sh +``` + +## Validation Checkpoints +Run these after key steps to catch failures early before proceeding to the next stage. + +**After packaging (`Scripts/package_app.sh`):** +```bash +# Confirm .app bundle structure is intact +ls -R build/HelloApp.app/Contents + +# Check that the binary is present and executable +file build/HelloApp.app/Contents/MacOS/HelloApp +``` + +**After signing (`Scripts/sign-and-notarize.sh` or ad-hoc dev signing):** +```bash +# Inspect signature and entitlements +codesign -dv --verbose=4 build/HelloApp.app + +# Verify the bundle passes Gatekeeper checks locally +spctl --assess --type execute --verbose build/HelloApp.app +``` + +**After notarization and stapling:** +```bash +# Confirm the staple ticket is attached +stapler validate build/HelloApp.app + +# Re-run Gatekeeper to confirm notarization is recognised +spctl --assess --type execute --verbose build/HelloApp.app +``` + +## Common Notarization Failures +| Symptom | Likely Cause | Recovery | +|---|---|---| +| `The software asset has already been uploaded` | Duplicate submission for same version | Bump `BUILD_NUMBER` in `version.env` and repackage. | +| `Package Invalid: Invalid Code Signing Entitlements` | Entitlements in `.entitlements` file don't match provisioning | Audit entitlements against Apple's allowed set; remove unsupported keys. | +| `The executable does not have the hardened runtime enabled` | Missing `--options runtime` flag in `codesign` invocation | Edit `sign-and-notarize.sh` to add `--options runtime` to all `codesign` calls. | +| Notarization hangs / no status email | `xcrun notarytool` network or credential issue | Run `xcrun notarytool history` to check status; re-export App Store Connect API key if expired. | +| `stapler validate` fails after successful notarization | Ticket not yet propagated | Wait ~60 s, then re-run `xcrun stapler staple`. | + +## Templates +- `assets/templates/package_app.sh`: Build binaries, create the .app bundle, copy resources, sign. +- `assets/templates/compile_and_run.sh`: Dev loop to kill running app, package, launch. +- `assets/templates/build_icon.sh`: Generate .icns from an Icon Composer file (requires Xcode install). +- `assets/templates/sign-and-notarize.sh`: Notarize, staple, and zip a release build. +- `assets/templates/make_appcast.sh`: Generate Sparkle appcast entries for updates. +- `assets/templates/setup_dev_signing.sh`: Create a stable dev code-signing identity. +- `assets/templates/launch.sh`: Simple launcher for a packaged .app. +- `assets/templates/version.env`: Example version file consumed by packaging scripts. +- `assets/templates/bootstrap/`: Minimal SwiftPM macOS app skeleton (Package.swift, Sources/, version.env). + +## Notes +- Keep entitlements and signing configuration explicit; edit the template scripts instead of reimplementing. +- Remove Sparkle steps if you do not use Sparkle for updates. +- Sparkle relies on the bundle build number (`CFBundleVersion`), so `BUILD_NUMBER` in `version.env` must increase for each update. +- For menu bar apps, set `MENU_BAR_APP=1` when packaging to emit `LSUIElement` in Info.plist. diff --git a/skills/macos-spm-app-packaging/agents/openai.yaml b/skills/macos-spm-app-packaging/agents/openai.yaml new file mode 100644 index 00000000..71c820aa --- /dev/null +++ b/skills/macos-spm-app-packaging/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "macOS SwiftPM Packaging" + short_description: "Package SwiftPM macOS apps" + default_prompt: "Use $macos-spm-app-packaging to scaffold or package a SwiftPM-based macOS app without Xcode." diff --git a/skills/macos-spm-app-packaging/assets/templates/bootstrap/Package.swift b/skills/macos-spm-app-packaging/assets/templates/bootstrap/Package.swift new file mode 100644 index 00000000..3207b8e6 --- /dev/null +++ b/skills/macos-spm-app-packaging/assets/templates/bootstrap/Package.swift @@ -0,0 +1,17 @@ +// swift-tools-version: 6.2 +import PackageDescription + +let package = Package( + name: "MyApp", + platforms: [ + .macOS(.v14), + ], + targets: [ + .executableTarget( + name: "MyApp", + path: "Sources/MyApp", + resources: [ + .process("Resources"), + ]) + ] +) diff --git a/skills/macos-spm-app-packaging/assets/templates/bootstrap/Sources/MyApp/Resources/.keep b/skills/macos-spm-app-packaging/assets/templates/bootstrap/Sources/MyApp/Resources/.keep new file mode 100644 index 00000000..e69de29b diff --git a/skills/macos-spm-app-packaging/assets/templates/bootstrap/Sources/MyApp/main.swift b/skills/macos-spm-app-packaging/assets/templates/bootstrap/Sources/MyApp/main.swift new file mode 100644 index 00000000..e3170169 --- /dev/null +++ b/skills/macos-spm-app-packaging/assets/templates/bootstrap/Sources/MyApp/main.swift @@ -0,0 +1,11 @@ +import SwiftUI + +@main +struct MyApp: App { + var body: some Scene { + WindowGroup { + Text("Hello from MyApp") + .padding() + } + } +} diff --git a/skills/macos-spm-app-packaging/assets/templates/bootstrap/version.env b/skills/macos-spm-app-packaging/assets/templates/bootstrap/version.env new file mode 100644 index 00000000..6c5435e9 --- /dev/null +++ b/skills/macos-spm-app-packaging/assets/templates/bootstrap/version.env @@ -0,0 +1,2 @@ +MARKETING_VERSION=0.1.0 +BUILD_NUMBER=1 diff --git a/skills/macos-spm-app-packaging/assets/templates/build_icon.sh b/skills/macos-spm-app-packaging/assets/templates/build_icon.sh new file mode 100644 index 00000000..464c485e --- /dev/null +++ b/skills/macos-spm-app-packaging/assets/templates/build_icon.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -euo pipefail + +ICON_FILE=${1:-Icon.icon} +BASENAME=${2:-Icon} +OUT_ROOT=${3:-build/icon} +XCODE_APP=${XCODE_APP:-/Applications/Xcode.app} + +ICTOOL="$XCODE_APP/Contents/Applications/Icon Composer.app/Contents/Executables/ictool" +if [[ ! -x "$ICTOOL" ]]; then + ICTOOL="$XCODE_APP/Contents/Applications/Icon Composer.app/Contents/Executables/icontool" +fi +if [[ ! -x "$ICTOOL" ]]; then + echo "ictool/icontool not found. Set XCODE_APP if Xcode is elsewhere." >&2 + exit 1 +fi + +ICONSET_DIR="$OUT_ROOT/${BASENAME}.iconset" +TMP_DIR="$OUT_ROOT/tmp" +mkdir -p "$ICONSET_DIR" "$TMP_DIR" + +MASTER_ART="$TMP_DIR/icon_art_824.png" +MASTER_1024="$TMP_DIR/icon_1024.png" + +# Render inner art (no margin) with macOS Default appearance. +"$ICTOOL" "$ICON_FILE" \ + --export-preview macOS Default 824 824 1 -45 "$MASTER_ART" + +# Pad to 1024x1024 with transparent border. +sips --padToHeightWidth 1024 1024 "$MASTER_ART" --out "$MASTER_1024" >/dev/null + +# Generate required sizes. +sizes=(16 32 64 128 256 512 1024) +for sz in "${sizes[@]}"; do + out="$ICONSET_DIR/icon_${sz}x${sz}.png" + sips -z "$sz" "$sz" "$MASTER_1024" --out "$out" >/dev/null + if [[ "$sz" -ne 1024 ]]; then + dbl=$((sz*2)) + out2="$ICONSET_DIR/icon_${sz}x${sz}@2x.png" + sips -z "$dbl" "$dbl" "$MASTER_1024" --out "$out2" >/dev/null + fi +done + +# 512x512@2x already covered by 1024; ensure it exists. +cp "$MASTER_1024" "$ICONSET_DIR/icon_512x512@2x.png" + +iconutil -c icns "$ICONSET_DIR" -o Icon.icns + +echo "Icon.icns generated at $(pwd)/Icon.icns" diff --git a/skills/macos-spm-app-packaging/assets/templates/compile_and_run.sh b/skills/macos-spm-app-packaging/assets/templates/compile_and_run.sh new file mode 100644 index 00000000..8bec4bc1 --- /dev/null +++ b/skills/macos-spm-app-packaging/assets/templates/compile_and_run.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# Kill running instances, package, relaunch, verify. +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +APP_NAME=${APP_NAME:-MyApp} +APP_BUNDLE="${ROOT_DIR}/${APP_NAME}.app" +APP_PROCESS_PATTERN="${APP_NAME}.app/Contents/MacOS/${APP_NAME}" +DEBUG_PROCESS_PATTERN="${ROOT_DIR}/.build/debug/${APP_NAME}" +RELEASE_PROCESS_PATTERN="${ROOT_DIR}/.build/release/${APP_NAME}" +RUN_TESTS=0 +RELEASE_ARCHES="" + +log() { printf '%s\n' "$*"; } +fail() { printf 'ERROR: %s\n' "$*" >&2; exit 1; } + +for arg in "$@"; do + case "${arg}" in + --test|-t) RUN_TESTS=1 ;; + --release-universal) RELEASE_ARCHES="arm64 x86_64" ;; + --release-arches=*) RELEASE_ARCHES="${arg#*=}" ;; + --help|-h) + log "Usage: $(basename "$0") [--test] [--release-universal] [--release-arches=\"arm64 x86_64\"]" + exit 0 + ;; + esac +done + +log "==> Killing existing ${APP_NAME} instances" +pkill -f "${APP_PROCESS_PATTERN}" 2>/dev/null || true +pkill -f "${DEBUG_PROCESS_PATTERN}" 2>/dev/null || true +pkill -f "${RELEASE_PROCESS_PATTERN}" 2>/dev/null || true +pkill -x "${APP_NAME}" 2>/dev/null || true + +if [[ "${RUN_TESTS}" == "1" ]]; then + log "==> swift test" + swift test -q +fi + +HOST_ARCH="$(uname -m)" +ARCHES_VALUE="${HOST_ARCH}" +if [[ -n "${RELEASE_ARCHES}" ]]; then + ARCHES_VALUE="${RELEASE_ARCHES}" +fi + +log "==> package app" +SIGNING_MODE=adhoc ARCHES="${ARCHES_VALUE}" "${ROOT_DIR}/Scripts/package_app.sh" release + +log "==> launch app" +if ! open "${APP_BUNDLE}"; then + log "WARN: open failed; launching binary directly." + "${APP_BUNDLE}/Contents/MacOS/${APP_NAME}" >/dev/null 2>&1 & + disown +fi + +for _ in {1..10}; do + if pgrep -f "${APP_PROCESS_PATTERN}" >/dev/null 2>&1; then + log "OK: ${APP_NAME} is running." + exit 0 + fi + sleep 0.4 +done +fail "App exited immediately. Check crash logs in Console.app (User Reports)." diff --git a/skills/macos-spm-app-packaging/assets/templates/launch.sh b/skills/macos-spm-app-packaging/assets/templates/launch.sh new file mode 100644 index 00000000..92af4e19 --- /dev/null +++ b/skills/macos-spm-app-packaging/assets/templates/launch.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +APP_NAME=${APP_NAME:-MyApp} +APP_PATH="$PROJECT_ROOT/${APP_NAME}.app" + +echo "==> Killing existing ${APP_NAME} instances" +pkill -x "$APP_NAME" || pkill -f "${APP_NAME}.app" || true +sleep 0.5 + +if [[ ! -d "$APP_PATH" ]]; then + echo "ERROR: ${APP_NAME}.app not found at $APP_PATH" + echo "Run ./Scripts/package_app.sh first to build the app" + exit 1 +fi + +echo "==> Launching ${APP_NAME} from $APP_PATH" +open -n "$APP_PATH" + +sleep 1 +if pgrep -x "$APP_NAME" > /dev/null; then + echo "OK: ${APP_NAME} is running." +else + echo "ERROR: App exited immediately. Check crash logs in Console.app (User Reports)." + exit 1 +fi diff --git a/skills/macos-spm-app-packaging/assets/templates/make_appcast.sh b/skills/macos-spm-app-packaging/assets/templates/make_appcast.sh new file mode 100644 index 00000000..1dce6d96 --- /dev/null +++ b/skills/macos-spm-app-packaging/assets/templates/make_appcast.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT=$(cd "$(dirname "$0")/.." && pwd) +ZIP=${1:? +"Usage: $0 MyApp-.zip"} +FEED_URL=${2:-"https://example.com/appcast.xml"} +PRIVATE_KEY_FILE=${SPARKLE_PRIVATE_KEY_FILE:-} +if [[ -z "$PRIVATE_KEY_FILE" ]]; then + echo "Set SPARKLE_PRIVATE_KEY_FILE to your ed25519 private key (Sparkle)." >&2 + exit 1 +fi +if [[ ! -f "$ZIP" ]]; then + echo "Zip not found: $ZIP" >&2 + exit 1 +fi + +ZIP_DIR=$(cd "$(dirname "$ZIP")" && pwd) +ZIP_NAME=$(basename "$ZIP") +ZIP_BASE="${ZIP_NAME%.zip}" +VERSION=${SPARKLE_RELEASE_VERSION:-} +if [[ -z "$VERSION" ]]; then + if [[ "$ZIP_NAME" =~ ^[^-]+-([0-9]+(\.[0-9]+){1,2}([-.][^.]*)?)\.zip$ ]]; then + VERSION="${BASH_REMATCH[1]}" + else + echo "Could not infer version from $ZIP_NAME; set SPARKLE_RELEASE_VERSION." >&2 + exit 1 + fi +fi + +NOTES_HTML="${ZIP_DIR}/${ZIP_BASE}.html" +KEEP_NOTES=${KEEP_SPARKLE_NOTES:-0} +if [[ -x "$ROOT/Scripts/changelog-to-html.sh" ]]; then + "$ROOT/Scripts/changelog-to-html.sh" "$VERSION" >"$NOTES_HTML" +else + cat >"$NOTES_HTML" < + + +${ZIP_BASE} + +

${ZIP_BASE}

+

Release notes not provided.

+ + +HTML +fi +cleanup() { + if [[ -n "${WORK_DIR:-}" ]]; then + rm -rf "$WORK_DIR" + fi + if [[ "$KEEP_NOTES" != "1" ]]; then + rm -f "$NOTES_HTML" + fi +} +trap cleanup EXIT + +DOWNLOAD_URL_PREFIX=${SPARKLE_DOWNLOAD_URL_PREFIX:-"https://example.com/downloads/v${VERSION}/"} + +if ! command -v generate_appcast >/dev/null; then + echo "generate_appcast not found in PATH. Install Sparkle tools." >&2 + exit 1 +fi + +WORK_DIR=$(mktemp -d /tmp/appcast.XXXXXX) + +cp "$ROOT/appcast.xml" "$WORK_DIR/appcast.xml" +cp "$ZIP" "$WORK_DIR/$ZIP_NAME" +cp "$NOTES_HTML" "$WORK_DIR/$ZIP_BASE.html" + +pushd "$WORK_DIR" >/dev/null +generate_appcast \ + --ed-key-file "$PRIVATE_KEY_FILE" \ + --download-url-prefix "$DOWNLOAD_URL_PREFIX" \ + --embed-release-notes \ + --link "$FEED_URL" \ + "$WORK_DIR" +popd >/dev/null + +cp "$WORK_DIR/appcast.xml" "$ROOT/appcast.xml" + +echo "Appcast generated (appcast.xml). Upload alongside $ZIP at $FEED_URL" diff --git a/skills/macos-spm-app-packaging/assets/templates/package_app.sh b/skills/macos-spm-app-packaging/assets/templates/package_app.sh new file mode 100644 index 00000000..1175cbc3 --- /dev/null +++ b/skills/macos-spm-app-packaging/assets/templates/package_app.sh @@ -0,0 +1,206 @@ +#!/usr/bin/env bash +set -euo pipefail + +CONF=${1:-release} +ROOT=$(cd "$(dirname "$0")/.." && pwd) +cd "$ROOT" + +APP_NAME=${APP_NAME:-MyApp} +BUNDLE_ID=${BUNDLE_ID:-com.example.myapp} +MACOS_MIN_VERSION=${MACOS_MIN_VERSION:-14.0} +MENU_BAR_APP=${MENU_BAR_APP:-0} +SIGNING_MODE=${SIGNING_MODE:-} +APP_IDENTITY=${APP_IDENTITY:-} + +if [[ -f "$ROOT/version.env" ]]; then + source "$ROOT/version.env" +else + MARKETING_VERSION=${MARKETING_VERSION:-0.1.0} + BUILD_NUMBER=${BUILD_NUMBER:-1} +fi + +ARCH_LIST=( ${ARCHES:-} ) +if [[ ${#ARCH_LIST[@]} -eq 0 ]]; then + HOST_ARCH=$(uname -m) + ARCH_LIST=("$HOST_ARCH") +fi + +for ARCH in "${ARCH_LIST[@]}"; do + swift build -c "$CONF" --arch "$ARCH" +done + +APP="$ROOT/${APP_NAME}.app" +rm -rf "$APP" +mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources" "$APP/Contents/Frameworks" + +# Convert Icon.icon to Icon.icns if present (requires iconutil). +ICON_SOURCE="$ROOT/Icon.icon" +ICON_TARGET="$ROOT/Icon.icns" +if [[ -f "$ICON_SOURCE" ]]; then + iconutil --convert icns --output "$ICON_TARGET" "$ICON_SOURCE" +fi + +LSUI_VALUE="false" +if [[ "$MENU_BAR_APP" == "1" ]]; then + LSUI_VALUE="true" +fi + +BUILD_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") +GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") + +cat > "$APP/Contents/Info.plist" < + + + + CFBundleName${APP_NAME} + CFBundleDisplayName${APP_NAME} + CFBundleIdentifier${BUNDLE_ID} + CFBundleExecutable${APP_NAME} + CFBundlePackageTypeAPPL + CFBundleShortVersionString${MARKETING_VERSION} + CFBundleVersion${BUILD_NUMBER} + LSMinimumSystemVersion${MACOS_MIN_VERSION} + LSUIElement<${LSUI_VALUE}/> + CFBundleIconFileIcon + BuildTimestamp${BUILD_TIMESTAMP} + GitCommit${GIT_COMMIT} + + +PLIST + +build_product_path() { + local name="$1" + local arch="$2" + case "$arch" in + arm64|x86_64) echo ".build/${arch}-apple-macosx/$CONF/$name" ;; + *) echo ".build/$CONF/$name" ;; + esac +} + +verify_binary_arches() { + local binary="$1"; shift + local expected=("$@") + local actual + actual=$(lipo -archs "$binary") + local actual_count expected_count + actual_count=$(wc -w <<<"$actual" | tr -d ' ') + expected_count=${#expected[@]} + if [[ "$actual_count" -ne "$expected_count" ]]; then + echo "ERROR: $binary arch mismatch (expected: ${expected[*]}, actual: ${actual})" >&2 + exit 1 + fi + for arch in "${expected[@]}"; do + if [[ "$actual" != *"$arch"* ]]; then + echo "ERROR: $binary missing arch $arch (have: ${actual})" >&2 + exit 1 + fi + done +} + +install_binary() { + local name="$1" + local dest="$2" + local binaries=() + for arch in "${ARCH_LIST[@]}"; do + local src + src=$(build_product_path "$name" "$arch") + if [[ ! -f "$src" ]]; then + echo "ERROR: Missing ${name} build for ${arch} at ${src}" >&2 + exit 1 + fi + binaries+=("$src") + done + if [[ ${#ARCH_LIST[@]} -gt 1 ]]; then + lipo -create "${binaries[@]}" -output "$dest" + else + cp "${binaries[0]}" "$dest" + fi + chmod +x "$dest" + verify_binary_arches "$dest" "${ARCH_LIST[@]}" +} + +install_binary "$APP_NAME" "$APP/Contents/MacOS/$APP_NAME" + +# Bundle app resources (if any). +APP_RESOURCES_DIR="$ROOT/Sources/$APP_NAME/Resources" +if [[ -d "$APP_RESOURCES_DIR" ]]; then + cp -R "$APP_RESOURCES_DIR/." "$APP/Contents/Resources/" +fi + +# SwiftPM resource bundles are emitted next to the built binary. +PREFERRED_BUILD_DIR="$(dirname "$(build_product_path "$APP_NAME" "${ARCH_LIST[0]}")")" +shopt -s nullglob +SWIFTPM_BUNDLES=("${PREFERRED_BUILD_DIR}/"*.bundle) +shopt -u nullglob +if [[ ${#SWIFTPM_BUNDLES[@]} -gt 0 ]]; then + for bundle in "${SWIFTPM_BUNDLES[@]}"; do + cp -R "$bundle" "$APP/Contents/Resources/" + done +fi + +# Embed frameworks if any exist in the build folder. +FRAMEWORK_DIRS=(".build/$CONF" ".build/${ARCH_LIST[0]}-apple-macosx/$CONF") +for dir in "${FRAMEWORK_DIRS[@]}"; do + if compgen -G "${dir}/*.framework" >/dev/null; then + cp -R "${dir}/"*.framework "$APP/Contents/Frameworks/" + chmod -R a+rX "$APP/Contents/Frameworks" + install_name_tool -add_rpath "@executable_path/../Frameworks" "$APP/Contents/MacOS/$APP_NAME" + break + fi +done + +if [[ -f "$ICON_TARGET" ]]; then + cp "$ICON_TARGET" "$APP/Contents/Resources/Icon.icns" +fi + +# Ensure contents are writable before stripping attributes and signing. +chmod -R u+w "$APP" + +# Strip extended attributes to prevent AppleDouble files that break code sealing. +xattr -cr "$APP" +find "$APP" -name '._*' -delete + +ENTITLEMENTS_DIR="$ROOT/.build/entitlements" +DEFAULT_ENTITLEMENTS="$ENTITLEMENTS_DIR/${APP_NAME}.entitlements" +mkdir -p "$ENTITLEMENTS_DIR" + +APP_ENTITLEMENTS=${APP_ENTITLEMENTS:-$DEFAULT_ENTITLEMENTS} +if [[ ! -f "$APP_ENTITLEMENTS" ]]; then + cat > "$APP_ENTITLEMENTS" < + + + + + + +PLIST +fi + +if [[ "$SIGNING_MODE" == "adhoc" || -z "$APP_IDENTITY" ]]; then + CODESIGN_ARGS=(--force --sign "-") +else + CODESIGN_ARGS=(--force --timestamp --options runtime --sign "$APP_IDENTITY") +fi + +# Sign embedded frameworks and their nested binaries before the app bundle. +sign_frameworks() { + local fw + for fw in "$APP/Contents/Frameworks/"*.framework; do + if [[ ! -d "$fw" ]]; then + continue + fi + while IFS= read -r -d '' bin; do + codesign "${CODESIGN_ARGS[@]}" "$bin" + done < <(find "$fw" -type f -perm -111 -print0) + codesign "${CODESIGN_ARGS[@]}" "$fw" + done +} +sign_frameworks + +codesign "${CODESIGN_ARGS[@]}" \ + --entitlements "$APP_ENTITLEMENTS" \ + "$APP" + +echo "Created $APP" diff --git a/skills/macos-spm-app-packaging/assets/templates/setup_dev_signing.sh b/skills/macos-spm-app-packaging/assets/templates/setup_dev_signing.sh new file mode 100644 index 00000000..42014d16 --- /dev/null +++ b/skills/macos-spm-app-packaging/assets/templates/setup_dev_signing.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# Setup stable development code signing to reduce keychain prompts. +set -euo pipefail + +APP_NAME=${APP_NAME:-MyApp} +CERT_NAME="${APP_NAME} Development" + +if security find-certificate -c "$CERT_NAME" >/dev/null 2>&1; then + echo "Certificate '$CERT_NAME' already exists." + echo "Export this in your shell profile:" + echo " export APP_IDENTITY='$CERT_NAME'" + exit 0 +fi + +echo "Creating self-signed certificate '$CERT_NAME'..." + +TEMP_CONFIG=$(mktemp) +trap "rm -f $TEMP_CONFIG" EXIT + +cat > "$TEMP_CONFIG" </dev/null + +openssl pkcs12 -export -out /tmp/dev.p12 \ + -inkey /tmp/dev.key -in /tmp/dev.crt \ + -passout pass: 2>/dev/null + +security import /tmp/dev.p12 -k ~/Library/Keychains/login.keychain-db \ + -T /usr/bin/codesign -T /usr/bin/security + +rm -f /tmp/dev.{key,crt,p12} + +echo "" +echo "Trust this certificate for code signing in Keychain Access." +echo "Then export in your shell profile:" +echo " export APP_IDENTITY='$CERT_NAME'" diff --git a/skills/macos-spm-app-packaging/assets/templates/sign-and-notarize.sh b/skills/macos-spm-app-packaging/assets/templates/sign-and-notarize.sh new file mode 100644 index 00000000..2e74bbed --- /dev/null +++ b/skills/macos-spm-app-packaging/assets/templates/sign-and-notarize.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +set -euo pipefail + +APP_NAME=${APP_NAME:-MyApp} +APP_IDENTITY=${APP_IDENTITY:-"Developer ID Application: Example (TEAMID)"} +APP_BUNDLE="${APP_NAME}.app" +ROOT=$(cd "$(dirname "$0")/.." && pwd) +source "$ROOT/version.env" +ZIP_NAME="${APP_NAME}-${MARKETING_VERSION}.zip" + +if [[ -z "${APP_STORE_CONNECT_API_KEY_P8:-}" || -z "${APP_STORE_CONNECT_KEY_ID:-}" || -z "${APP_STORE_CONNECT_ISSUER_ID:-}" ]]; then + echo "Missing APP_STORE_CONNECT_* env vars (API key, key id, issuer id)." >&2 + exit 1 +fi + +echo "$APP_STORE_CONNECT_API_KEY_P8" | sed 's/\\n/\n/g' > /tmp/app-store-connect-key.p8 +trap 'rm -f /tmp/app-store-connect-key.p8 /tmp/${APP_NAME}Notarize.zip' EXIT + +ARCHES_VALUE=${ARCHES:-"arm64 x86_64"} +ARCH_LIST=( ${ARCHES_VALUE} ) +for ARCH in "${ARCH_LIST[@]}"; do + swift build -c release --arch "$ARCH" +done +ARCHES="${ARCHES_VALUE}" "$ROOT/Scripts/package_app.sh" release + +ENTITLEMENTS_DIR="$ROOT/.build/entitlements" +APP_ENTITLEMENTS="${APP_ENTITLEMENTS:-${ENTITLEMENTS_DIR}/${APP_NAME}.entitlements}" + +codesign --force --timestamp --options runtime --sign "$APP_IDENTITY" \ + --entitlements "$APP_ENTITLEMENTS" \ + "$APP_BUNDLE" + +DITTO_BIN=${DITTO_BIN:-/usr/bin/ditto} +"$DITTO_BIN" --norsrc -c -k --keepParent "$APP_BUNDLE" "/tmp/${APP_NAME}Notarize.zip" + +xcrun notarytool submit "/tmp/${APP_NAME}Notarize.zip" \ + --key /tmp/app-store-connect-key.p8 \ + --key-id "$APP_STORE_CONNECT_KEY_ID" \ + --issuer "$APP_STORE_CONNECT_ISSUER_ID" \ + --wait + +xcrun stapler staple "$APP_BUNDLE" + +xattr -cr "$APP_BUNDLE" +find "$APP_BUNDLE" -name '._*' -delete + +"$DITTO_BIN" --norsrc -c -k --keepParent "$APP_BUNDLE" "$ZIP_NAME" + +spctl -a -t exec -vv "$APP_BUNDLE" +stapler validate "$APP_BUNDLE" + +echo "Done: $ZIP_NAME" diff --git a/skills/macos-spm-app-packaging/assets/templates/version.env b/skills/macos-spm-app-packaging/assets/templates/version.env new file mode 100644 index 00000000..6c5435e9 --- /dev/null +++ b/skills/macos-spm-app-packaging/assets/templates/version.env @@ -0,0 +1,2 @@ +MARKETING_VERSION=0.1.0 +BUILD_NUMBER=1 diff --git a/skills/macos-spm-app-packaging/references/packaging.md b/skills/macos-spm-app-packaging/references/packaging.md new file mode 100644 index 00000000..acaf317c --- /dev/null +++ b/skills/macos-spm-app-packaging/references/packaging.md @@ -0,0 +1,17 @@ +# Packaging notes + +## Build output paths +SwiftPM places binaries under: +- `.build/-apple-macosx//` for arch-specific builds +- `.build//` for some products (frameworks/tools) + +Use `ARCHES="arm64 x86_64"` with `swift build` to produce universal binaries. + +## Common environment variables (used by templates) +- `APP_NAME`: App/binary name (for example, `MyApp`). +- `BUNDLE_ID`: Bundle identifier (for example, `com.example.myapp`). +- `ARCHES`: Space-separated architectures (default: host arch). +- `SIGNING_MODE`: `adhoc` to avoid keychain prompts in dev. +- `APP_IDENTITY`: Codesigning identity name for release builds. +- `MACOS_MIN_VERSION`: Minimum macOS version for Info.plist. +- `MENU_BAR_APP`: Set to `1` to add `LSUIElement` to Info.plist. diff --git a/skills/macos-spm-app-packaging/references/release.md b/skills/macos-spm-app-packaging/references/release.md new file mode 100644 index 00000000..55930863 --- /dev/null +++ b/skills/macos-spm-app-packaging/references/release.md @@ -0,0 +1,32 @@ +# Release and notarization notes + +## Notarization requirements +- Install Xcode Command Line Tools (for `xcrun` and `notarytool`). +- Provide App Store Connect API credentials: + - `APP_STORE_CONNECT_API_KEY_P8` + - `APP_STORE_CONNECT_KEY_ID` + - `APP_STORE_CONNECT_ISSUER_ID` +- Provide a Developer ID Application identity in `APP_IDENTITY`. + +## Sparkle appcast (optional) +- Install Sparkle tools so `generate_appcast` is on PATH. +- Provide `SPARKLE_PRIVATE_KEY_FILE` (ed25519 key). +- The appcast script uses your zip artifact to create an updated `appcast.xml`. +- Sparkle compares `sparkle:version` (derived from `CFBundleVersion`), so bump `BUILD_NUMBER` for every release. + +## Tag and GitHub release (optional) +Use a versioned git tag and publish a GitHub release with the notarized zip (and appcast if you host it on GitHub Releases). + +Example flow: +``` +git tag v +git push origin v + +gh release create v CodexBar-.zip appcast.xml \ + --title "AppName " \ + --notes-file CHANGELOG.md +``` + +Notes: +- If you serve appcast from GitHub Releases or raw URLs, ensure the release is published and assets are accessible (no 404s). +- Prefer using a curated release notes file rather than dumping the full changelog. diff --git a/skills/macos-spm-app-packaging/references/scaffold.md b/skills/macos-spm-app-packaging/references/scaffold.md new file mode 100644 index 00000000..1eee900c --- /dev/null +++ b/skills/macos-spm-app-packaging/references/scaffold.md @@ -0,0 +1,79 @@ +# Scaffold a SwiftPM macOS app (no Xcode) + +## Steps +1) Create a repo and initialize SwiftPM: +``` +mkdir MyApp +cd MyApp +swift package init --type executable +``` + +2) Update `Package.swift` to target macOS and define an executable target for the app. + +3) Create the app entry point under `Sources/MyApp/`. +- Use SwiftUI if you want a windowed app with minimal AppKit glue. +- Use AppKit if you want a menu bar or accessory-style app. + +4) If you need app resources, add: +``` +resources: [.process("Resources")] +``` +and create `Sources/MyApp/Resources/`. + +5) Add a `version.env` file (used by packaging templates): +``` +MARKETING_VERSION=0.1.0 +BUILD_NUMBER=1 +``` + +6) Copy script templates from `assets/templates/` into your repo (for example, `Scripts/`). + +## Minimal Package.swift (example) +``` +// swift-tools-version: 6.2 +import PackageDescription + +let package = Package( + name: "MyApp", + platforms: [.macOS(.v14)], + targets: [ + .executableTarget( + name: "MyApp", + path: "Sources/MyApp", + resources: [ + .process("Resources") + ]) + ] +) +``` + +## Minimal SwiftUI entry point (example) +``` +import SwiftUI + +@main +struct MyApp: App { + var body: some Scene { + WindowGroup { + Text("Hello") + } + } +} +``` + +## Minimal AppKit entry point (example) +``` +import AppKit + +final class AppDelegate: NSObject, NSApplicationDelegate { + func applicationDidFinishLaunching(_ notification: Notification) { + // Initialize app state here. + } +} + +let app = NSApplication.shared +let delegate = AppDelegate() +app.delegate = delegate +app.setActivationPolicy(.regular) +app.run() +``` diff --git a/skills/orchestrate-batch-refactor/SKILL.md b/skills/orchestrate-batch-refactor/SKILL.md new file mode 100644 index 00000000..8fea757f --- /dev/null +++ b/skills/orchestrate-batch-refactor/SKILL.md @@ -0,0 +1,97 @@ +--- +name: "orchestrate-batch-refactor" +description: "Plan and execute large refactors with dependency-aware work packets and parallel analysis." +risk: safe +source: "Dimillian/Skills (MIT)" +date_added: "2026-03-25" +--- + +# Orchestrate Batch Refactor + +## Overview + +Use this skill to run high-throughput refactors safely. +Analyze scope in parallel, synthesize a single plan, then execute independent work packets with sub-agents. + +## When to Use + +- When a refactor spans many files or subsystems and needs clear work partitioning. +- When you need dependency-aware planning before parallel implementation. + +## Inputs + +- Repo path and target scope (paths, modules, or feature area) +- Goal type: refactor, rewrite, or hybrid +- Constraints: behavior parity, API stability, deadlines, test requirements + +## When to Use Parallelization + +- Use this skill for medium/large scope touching many files or subsystems. +- Skip multi-agent execution for tiny edits or highly coupled single-file work. + +## Core Workflow + +1. Define scope and success criteria. + - List target paths/modules and non-goals. + - State behavior constraints (for example: preserve external behavior). +2. Run parallel analysis first. + - Split target scope into analysis lanes. + - Spawn `explorer` sub-agents in parallel to analyze each lane. + - Ask each agent for: intent map, coupling risks, candidate work packets, required validations. +3. Build one dependency-aware plan. + - Merge explorer output into a single work graph. + - Create work packets with clear file ownership and validation commands. + - Sequence packets by dependency level; run only independent packets in parallel. +4. Execute with worker agents. + - Spawn one `worker` per independent packet. + - Assign explicit ownership (files/responsibility). + - Instruct every worker that they are not alone in the codebase and must ignore unrelated edits. +5. Integrate and verify. + - Review packet outputs, resolve overlaps, and run validation gates. + - Run targeted tests per packet, then broader suite for integrated scope. +6. Report and close. + - Summarize packet outcomes, key refactors, conflicts resolved, and residual risks. + +## Work Packet Rules + +- One owner per file per execution wave. +- No parallel edits on overlapping file sets. +- Keep packet goals narrow and measurable. +- Include explicit done criteria and required checks. +- Prefer behavior-preserving refactors unless user explicitly requests behavior change. + +## Planning Contract + +Every packet must include: + +1. Packet ID and objective. +2. Owned files. +3. Dependencies (none or packet IDs). +4. Risks and invariants to preserve. +5. Required checks. +6. Integration notes for main thread. + +Use [`references/work-packet-template.md`](references/work-packet-template.md) for the exact shape. + +## Agent Prompting Contract + +- Use the prompt templates in [`references/agent-prompt-templates.md`](references/agent-prompt-templates.md). +- Explorer prompts focus on analysis and decomposition. +- Worker prompts focus on implementation and validation with strict ownership boundaries. + +## Safety Guardrails + +- Do not start worker execution before plan synthesis is complete. +- Do not parallelize across unresolved dependencies. +- Do not claim completion if any required packet check fails. +- Stop and re-plan when packet boundaries cause repeated merge conflicts. + +## Validation Strategy + +Run in this order: + +1. Packet-level checks (fast and scoped). +2. Cross-packet integration checks. +3. Full project safety checks when scope is broad. + +Prefer fast feedback loops, but never skip required behavior checks. diff --git a/skills/orchestrate-batch-refactor/agents/openai.yaml b/skills/orchestrate-batch-refactor/agents/openai.yaml new file mode 100644 index 00000000..2e838c6a --- /dev/null +++ b/skills/orchestrate-batch-refactor/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Batch Refactor Orchestrator" + short_description: "Plan and parallelize large refactors safely." + default_prompt: "Use $orchestrate-batch-refactor to analyze target scope, plan parallel workstreams, and execute multi-agent refactors safely." diff --git a/skills/orchestrate-batch-refactor/references/agent-prompt-templates.md b/skills/orchestrate-batch-refactor/references/agent-prompt-templates.md new file mode 100644 index 00000000..04ac091e --- /dev/null +++ b/skills/orchestrate-batch-refactor/references/agent-prompt-templates.md @@ -0,0 +1,53 @@ +# Agent Prompt Templates + +Use these templates when spawning sub-agents. + +## Explorer Prompt Template + +``` +Analyze the target scope and return decomposition guidance only. + +Scope: +- Paths/modules: +- Goal: +- Constraints: + +Return: +1. Intent map (what each area currently does) +2. Coupling and dependency risks +3. Candidate work packets with non-overlapping ownership +4. Validation commands per packet +5. Recommended execution order +``` + +## Worker Prompt Template + +``` +You own this packet and are not alone in the codebase. +Ignore unrelated edits by others and do not touch files outside ownership. + +Packet: +- ID: +- Objective: +- Owned files: +- Dependencies already completed: +- Invariants to preserve: +- Required checks: + +Execution requirements: +1. Implement only the packet objective. +2. Preserve specified invariants and external behavior. +3. Run required checks and report exact results. +4. Summarize changed files and any integration notes. +``` + +## Main Thread Synthesis Prompt Template + +``` +Merge explorer outputs into a single dependency-aware plan. +Produce: +1. Packet table with ownership and dependencies +2. Parallel execution waves (no overlap per wave) +3. Validation matrix by packet and integration stage +4. Risk list with mitigation actions +``` diff --git a/skills/orchestrate-batch-refactor/references/work-packet-template.md b/skills/orchestrate-batch-refactor/references/work-packet-template.md new file mode 100644 index 00000000..751bf007 --- /dev/null +++ b/skills/orchestrate-batch-refactor/references/work-packet-template.md @@ -0,0 +1,31 @@ +# Work Packet Template + +Use this template to define each packet before spawning workers. + +## Packet + +- `id`: +- `objective`: +- `mode`: `refactor` | `rewrite` | `hybrid` +- `owner_agent_type`: `worker` +- `owned_files`: +- `dependencies`: +- `invariants_to_preserve`: +- `out_of_scope`: +- `required_checks`: +- `integration_notes`: +- `done_criteria`: + +## Example + +- `id`: `P3` +- `objective`: "Extract duplicated parsing logic from thread reducers into shared helper" +- `mode`: `refactor` +- `owner_agent_type`: `worker` +- `owned_files`: `src/features/threads/hooks/threadReducer/*.ts` +- `dependencies`: `P1` +- `invariants_to_preserve`: "Thread ordering and hidden-thread filtering behavior" +- `out_of_scope`: "UI rendering components" +- `required_checks`: `npm run typecheck`, `npm run test -- src/features/threads/hooks` +- `integration_notes`: "Main thread verifies no overlapping helper names with existing util package" +- `done_criteria`: "No duplicated parsing block remains; all required checks pass" diff --git a/skills/project-skill-audit/SKILL.md b/skills/project-skill-audit/SKILL.md new file mode 100644 index 00000000..c1baabdb --- /dev/null +++ b/skills/project-skill-audit/SKILL.md @@ -0,0 +1,190 @@ +--- +name: project-skill-audit +description: Audit a project and recommend the highest-value skills to add or update. +risk: safe +source: "Dimillian/Skills (MIT)" +date_added: "2026-03-25" +--- + +# Project Skill Audit + +## Overview + +Audit the project's real recurring workflows before recommending skills. Prefer evidence from memory, rollout summaries, existing skill folders, and current repo conventions over generic brainstorming. + +Recommend updates before new skills when an existing project skill is already close to the needed behavior. + +## When to Use + +- When the user asks what skills a project needs or which existing skills should be updated. +- When recommendations should be grounded in project history, memory files, and local conventions. + +## Workflow + +1. Map the current project surface. + Identify the repo root and read the most relevant project guidance first, such as `AGENTS.md`, `README.md`, roadmap/ledger files, and local docs that define workflows or validation expectations. + +2. Build the memory/session path first. + Resolve the memory base as `$CODEX_HOME` when set, otherwise default to `~/.codex`. + Use these locations: + - memory index: `$CODEX_HOME/memories/MEMORY.md` or `~/.codex/memories/MEMORY.md` + - rollout summaries: `$CODEX_HOME/memories/rollout_summaries/` + - raw sessions: `$CODEX_HOME/sessions/` or `~/.codex/sessions/` + +3. Read project past sessions in this order. + If the runtime prompt already includes a memory summary, start there. + Then search `MEMORY.md` for: + - repo name + - repo basename + - current `cwd` + - important module or file names + Open only the 1-3 most relevant rollout summaries first. + Fall back to raw session JSONL only when the summaries are missing the exact evidence you need. + +4. Scan existing project-local skills before suggesting anything new. + Check these locations relative to the current repo root: + - `.agents/skills` + - `.codex/skills` + - `skills` + Read both `SKILL.md` and `agents/openai.yaml` when present. + +5. Compare project-local skills against recurring work. + Look for repeated patterns in past sessions: + - repeated validation sequences + - repeated failure shields + - recurring ownership boundaries + - repeated root-cause categories + - workflows that repeatedly require the same repo-specific context + If the pattern appears repeatedly and is not already well captured, it is a candidate skill. + +6. Separate `new skill` from `update existing skill`. + Recommend an update when an existing skill is already the right bucket but has stale triggers, missing guardrails, outdated paths, weak validation instructions, or incomplete scope. + Recommend a new skill only when the workflow is distinct enough that stretching an existing skill would make it vague or confusing. + +7. Check for overlap with global skills only after reviewing project-local skills. + Use `$CODEX_HOME/skills` and `$CODEX_HOME/skills/public` to avoid proposing project-local skills for workflows already solved well by a generic shared skill. + Do not reject a project-local skill just because a global skill exists; project-specific guardrails can still justify a local specialization. + +## Session Analysis + +### 1. Search memory index first + +- Search `MEMORY.md` with `rg` using the repo name, basename, and `cwd`. +- Prefer entries that already cite rollout summaries with the same repo path. +- Capture: + - repeated workflows + - validation commands + - failure shields + - ownership boundaries + - milestone or roadmap coupling + +### 2. Open targeted rollout summaries + +- Open the most relevant summary files under `memories/rollout_summaries/`. +- Prefer summaries whose filenames, `cwd`, or `keywords` match the current project. +- Extract: + - what the user asked for repeatedly + - what steps kept recurring + - what broke repeatedly + - what commands proved correctness + - what project-specific context had to be rediscovered + +### 3. Use raw sessions only as a fallback + +- Only search `sessions/` JSONL files if rollout summaries are missing a concrete detail. +- Search by: + - exact `cwd` + - repo basename + - thread ID from a rollout summary + - specific file paths or commands +- Use raw sessions to recover exact prompts, command sequences, diffs, or failure text, not to replace the summary pass. + +### 4. Turn session evidence into skill candidates + +- A candidate `new skill` should correspond to a repeated workflow, not just a repeated topic. +- A candidate `skill update` should correspond to a workflow already covered by a local skill whose triggers, guardrails, or validation instructions no longer match the recorded sessions. +- Prefer concrete evidence such as: + - "this validation sequence appeared in 4 sessions" + - "this ownership confusion repeated across extractor and runtime fixes" + - "the same local script and telemetry probes had to be rediscovered repeatedly" + +## Recommendation Rules + +- Recommend a new skill when: + - the same repo-specific workflow or failure mode appears multiple times across sessions + - success depends on project-specific paths, scripts, ownership rules, or validation steps + - the workflow benefits from strong defaults or failure shields + +- Recommend an update when: + - an existing project-local skill already covers most of the need + - `SKILL.md` and `agents/openai.yaml` drift from each other + - paths, scripts, validation commands, or milestone references are stale + - the skill body is too generic to reflect how the project is actually worked on + +- Do not recommend a skill when: + - the pattern is a one-off bug rather than a reusable workflow + - a generic global skill already fits with no meaningful project-specific additions + - the workflow has not recurred enough to justify the maintenance cost + +## What To Scan + +- Past sessions and memory: + - memory summary already in context, if any + - `$CODEX_HOME/memories/MEMORY.md` or `~/.codex/memories/MEMORY.md` + - the 1-3 most relevant rollout summaries for the current repo + - raw `$CODEX_HOME/sessions` or `~/.codex/sessions` JSONL files only if summaries are insufficient + +- Project-local skill surface: + - `./.agents/skills/*/SKILL.md` + - `./.agents/skills/*/agents/openai.yaml` + - `./.codex/skills/*/SKILL.md` + - `./skills/*/SKILL.md` + +- Project conventions: + - `AGENTS.md` + - `README.md` + - roadmap, ledger, architecture, or validation docs + - current worktree or recent touched areas if needed for context + +## Output Expectations + +Return a compact audit with: + +1. `Existing skills` + List the project-local skills found and the main workflow each one covers. + +2. `Suggested updates` + For each update candidate, include: + - skill name + - why it is incomplete or stale + - the highest-value change to make + +3. `Suggested new skills` + For each new skill, include: + - recommended skill name + - why it should exist + - what would trigger it + - the core workflow it should encode + +4. `Priority order` + Rank the top recommendations by expected value. + +## Naming Guidance + +- Prefer short hyphen-case names. +- Use project prefixes for project-local skills when that improves clarity. +- Prefer verb-led or action-oriented names over vague nouns. + +## Failure Shields + +- Do not invent recurring patterns without session or repo evidence. +- Do not recommend duplicate skills when an update to an existing skill would suffice. +- Do not rely on a single memory note if the current repo clearly evolved since then. +- Do not bulk-load all rollout summaries; stay targeted. +- Do not skip rollout summaries and jump straight to raw sessions unless the summaries are insufficient. +- Do not recommend skills from themes alone; recommendations should come from repeated procedures, repeated validation flows, or repeated failure modes. +- Do not confuse a project's current implementation tasks with its reusable skill needs. + +## Follow-up + +If the user asks to actually create or update one of the recommended skills, switch to [$skill-creator](/Users/dimillian/.codex/skills/.system/skill-creator/SKILL.md) and implement the chosen skill rather than continuing the audit. diff --git a/skills/project-skill-audit/agents/openai.yaml b/skills/project-skill-audit/agents/openai.yaml new file mode 100644 index 00000000..6d607843 --- /dev/null +++ b/skills/project-skill-audit/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Project Skill Audit" + short_description: "Audit project sessions and skill coverage" + default_prompt: "Use $project-skill-audit to analyze this project and recommend new skills or updates to existing ones." diff --git a/skills/react-component-performance/SKILL.md b/skills/react-component-performance/SKILL.md new file mode 100644 index 00000000..50dfa5aa --- /dev/null +++ b/skills/react-component-performance/SKILL.md @@ -0,0 +1,135 @@ +--- +name: react-component-performance +description: Diagnose slow React components and suggest targeted performance fixes. +risk: safe +source: "Dimillian/Skills (MIT)" +date_added: "2026-03-25" +--- + +# React Component Performance + +## Overview + +Identify render hotspots, isolate expensive updates, and apply targeted optimizations without changing UI behavior. + +## When to Use + +- When the user asks to profile or improve a slow React component. +- When you need to reduce re-renders, list lag, or expensive render work in React UI. + +## Workflow + +1. Reproduce or describe the slowdown. +2. Identify what triggers re-renders (state updates, props churn, effects). +3. Isolate fast-changing state from heavy subtrees. +4. Stabilize props and handlers; memoize where it pays off. +5. Reduce expensive work (computation, DOM size, list length). +6. **Validate**: open React DevTools Profiler → record the interaction → inspect the Flamegraph for components rendering longer than ~16 ms → compare against a pre-optimization baseline recording. + +## Checklist + +- Measure: use React DevTools Profiler or log renders; capture baseline. +- Find churn: identify state updated on a timer, scroll, input, or animation. +- Split: move ticking state into a child; keep heavy lists static. +- Memoize: wrap leaf rows with `memo` only when props are stable. +- Stabilize props: use `useCallback`/`useMemo` for handlers and derived values. +- Avoid derived work in render: precompute, or compute inside memoized helpers. +- Control list size: window/virtualize long lists; avoid rendering hidden items. +- Keys: ensure stable keys; avoid index when order can change. +- Effects: verify dependency arrays; avoid effects that re-run on every render. +- Style/layout: watch for expensive layout thrash or large Markdown/diff renders. + +## Optimization Patterns + +### Isolate ticking state + +Move a timer or animation counter into a child so the parent list never re-renders on each tick. + +```tsx +// ❌ Before – entire parent (and list) re-renders every second +function Dashboard({ items }: { items: Item[] }) { + const [tick, setTick] = useState(0); + useEffect(() => { + const id = setInterval(() => setTick(t => t + 1), 1000); + return () => clearInterval(id); + }, []); + return ( + <> + + {/* re-renders every second */} + + ); +} + +// ✅ After – only re-renders; list is untouched +function Clock() { + const [tick, setTick] = useState(0); + useEffect(() => { + const id = setInterval(() => setTick(t => t + 1), 1000); + return () => clearInterval(id); + }, []); + return {tick}s; +} + +function Dashboard({ items }: { items: Item[] }) { + return ( + <> + + + + ); +} +``` + +### Stabilize callbacks with `useCallback` + `memo` + +```tsx +// ❌ Before – new handler reference on every render busts Row memo +function List({ items }: { items: Item[] }) { + const handleClick = (id: string) => console.log(id); // new ref each render + return items.map(item => ); +} + +// ✅ After – stable handler; Row only re-renders when its own item changes +const Row = memo(({ item, onClick }: RowProps) => ( +
  • onClick(item.id)}>{item.name}
  • +)); + +function List({ items }: { items: Item[] }) { + const handleClick = useCallback((id: string) => console.log(id), []); + return items.map(item => ); +} +``` + +### Prefer derived data outside render + +```tsx +// ❌ Before – recomputes on every render +function Summary({ orders }: { orders: Order[] }) { + const total = orders.reduce((sum, o) => sum + o.amount, 0); // runs every render + return

    Total: {total}

    ; +} + +// ✅ After – recomputes only when orders changes +function Summary({ orders }: { orders: Order[] }) { + const total = useMemo(() => orders.reduce((sum, o) => sum + o.amount, 0), [orders]); + return

    Total: {total}

    ; +} +``` + +### Additional patterns + +- **Split rows**: extract list rows into memoized components with narrow props. +- **Defer heavy rendering**: lazy-render or collapse expensive content until expanded. + +## Profiling Validation Steps + +1. Open **React DevTools → Profiler** tab. +2. Click **Record**, perform the slow interaction, then **Stop**. +3. Switch to **Flamegraph** view; any bar labeled with a component and time > ~16 ms is a candidate. +4. Use **Ranked chart** to sort by self render time and target the top offenders. +5. Apply one optimization at a time, re-record, and compare render counts and durations against the baseline. + +## Example Reference + +Load `references/examples.md` when the user wants a concrete refactor example. diff --git a/skills/react-component-performance/agents/openai.yaml b/skills/react-component-performance/agents/openai.yaml new file mode 100644 index 00000000..448d3064 --- /dev/null +++ b/skills/react-component-performance/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "React Component Performance" + short_description: "Profile and fix React render issues" + default_prompt: "Use $react-component-performance to analyze and improve this React component's rendering performance." diff --git a/skills/react-component-performance/references/examples.md b/skills/react-component-performance/references/examples.md new file mode 100644 index 00000000..d26cf656 --- /dev/null +++ b/skills/react-component-performance/references/examples.md @@ -0,0 +1,88 @@ +# Examples + +## Isolate a ticking timer from a long list + +**Scenario:** A message list re-renders every second because a timer (`elapsedMs`) lives in the parent component. This causes visible jank on large lists. + +**Goal:** Keep UI identical but limit re-renders to the timer area. + +**Before (problematic pattern):** + +```tsx +function Messages({ items, isThinking, processingStartedAt }) { + const [elapsedMs, setElapsedMs] = useState(0); + + useEffect(() => { + if (!isThinking || !processingStartedAt) { + setElapsedMs(0); + return; + } + setElapsedMs(Date.now() - processingStartedAt); + const interval = window.setInterval(() => { + setElapsedMs(Date.now() - processingStartedAt); + }, 1000); + return () => window.clearInterval(interval); + }, [isThinking, processingStartedAt]); + + return ( +
    + {items.map((item) => ( + + ))} +
    {formatDurationMs(elapsedMs)}
    +
    + ); +} +``` + +**After (isolated ticking state):** + +```tsx +type WorkingIndicatorProps = { + isThinking: boolean; + processingStartedAt?: number | null; +}; + +const WorkingIndicator = memo(function WorkingIndicator({ + isThinking, + processingStartedAt = null, +}: WorkingIndicatorProps) { + const [elapsedMs, setElapsedMs] = useState(0); + + useEffect(() => { + if (!isThinking || !processingStartedAt) { + setElapsedMs(0); + return; + } + setElapsedMs(Date.now() - processingStartedAt); + const interval = window.setInterval(() => { + setElapsedMs(Date.now() - processingStartedAt); + }, 1000); + return () => window.clearInterval(interval); + }, [isThinking, processingStartedAt]); + + return
    {formatDurationMs(elapsedMs)}
    ; +}); + +function Messages({ items, isThinking, processingStartedAt }) { + return ( +
    + {items.map((item) => ( + + ))} + +
    + ); +} +``` + +**Why it helps:** Only the `WorkingIndicator` subtree re-renders every second. The list remains stable unless its props change. + +**Optional follow-ups:** + +- Wrap `MessageRow` in `memo` if props are stable. +- Use `useCallback` for handlers passed to rows to avoid re-render churn. +- Consider list virtualization if the list is very large. diff --git a/skills/simplify-code/SKILL.md b/skills/simplify-code/SKILL.md new file mode 100644 index 00000000..69ba8659 --- /dev/null +++ b/skills/simplify-code/SKILL.md @@ -0,0 +1,179 @@ +--- +name: simplify-code +description: "Review a diff for clarity and safe simplifications, then optionally apply low-risk fixes." +risk: safe +source: "Dimillian/Skills (MIT)" +date_added: "2026-03-25" +--- + +# Simplify Code + +Review changed code for reuse, quality, efficiency, and clarity issues. Use Codex sub-agents to review in parallel, then optionally apply only high-confidence, behavior-preserving fixes. + +## When to Use + +- When the user asks to simplify, clean up, refactor, or review changed code. +- When you want high-confidence, behavior-preserving improvements on a scoped diff. + +## Modes + +Choose the mode from the user's request: + +- `review-only`: user asks to review, audit, or check the changes +- `safe-fixes`: user asks to simplify, clean up, or refactor the changes +- `fix-and-validate`: same as `safe-fixes`, but also run the smallest relevant validation after edits + +If the user does not specify, default to: + +- `review-only` for "review", "audit", or "check" +- `safe-fixes` for "simplify", "clean up", or "refactor" + +## Step 1: Determine the Scope and Diff Command + +Prefer this scope order: + +1. Files or paths explicitly named by the user +2. Current git changes +3. Files edited earlier in the current Codex turn +4. Most recently modified tracked files, only if the user asked for a review but there is no diff + +If there is no clear scope, stop and say so briefly. + +When using git changes, determine the smallest correct diff command based on the repo state: + +- unstaged work: `git diff` +- staged work: `git diff --cached` +- branch or commit comparison explicitly requested by the user: use that exact diff target +- mixed staged and unstaged work: review both + +Do not assume `git diff HEAD` is the right default when a smaller diff is available. + +Before reviewing standards or applying fixes, read the repo's local instruction files and relevant project docs for the touched area. Prefer the closest applicable guidance, such as: + +- `AGENTS.md` +- repo workflow docs +- architecture or style docs for the touched module + +Use those instructions to distinguish real issues from intentional local patterns. + +## Step 2: Launch Four Review Sub-Agents in Parallel + +Use Codex sub-agents when the scope is large enough for parallel review to help. For a tiny diff or one very small file, it is acceptable to review locally instead. + +When spawning sub-agents: + +- give each sub-agent the same scope +- tell each sub-agent to inspect only its assigned review role +- ask for concise, structured findings only +- ask each sub-agent to report file, line or symbol, problem, recommended fix, and confidence + +Use four review roles. + +### Sub-Agent 1: Code Reuse Review + +Review the changes for reuse opportunities: + +1. Search for existing helpers, utilities, or shared abstractions that already solve the same problem. +2. Flag duplicated functions or near-duplicate logic introduced in the change. +3. Flag inline logic that should call an existing helper instead of re-implementing it. + +Recommended sub-agent role: `explorer` for broad codebase lookup, or `reviewer` if a stronger review pass is more useful than wide search. + +### Sub-Agent 2: Code Quality Review + +Review the same changes for code quality issues: + +1. Redundant state, cached values, or derived values stored unnecessarily +2. Parameter sprawl caused by threading new arguments through existing call chains +3. Copy-paste with slight variation that should become a shared abstraction +4. Leaky abstractions or ownership violations across module boundaries +5. Stringly-typed values where existing typed contracts, enums, or constants already exist + +Recommended sub-agent role: `reviewer` + +### Sub-Agent 3: Efficiency Review + +Review the same changes for efficiency issues: + +1. Repeated work, duplicate reads, duplicate API calls, or unnecessary recomputation +2. Sequential work that could safely run concurrently +3. New work added to startup, render, request, or other hot paths without clear need +4. Pre-checks for existence when the operation itself can be attempted directly and errors handled +5. Memory growth, missing cleanup, or listener/subscription leaks +6. Overly broad reads or scans when the code only needs a subset + +Recommended sub-agent role: `reviewer` + +### Sub-Agent 4: Clarity and Standards Review + +Review the same changes for clarity, local standards, and balance: + +1. Violations of local project conventions or module patterns +2. Unnecessary complexity, deep nesting, weak names, or redundant comments +3. Overly compact or clever code that reduces readability +4. Over-simplification that collapses separate concerns into one unclear unit +5. Dead code, dead abstractions, or indirection without value + +Recommended sub-agent role: `reviewer` + +Only report issues that materially improve maintainability, correctness, or cost. Do not churn code just to make it look different. + +## Step 3: Aggregate Findings + +Wait for all review sub-agents to complete, then merge their findings. + +Normalize findings into this shape: + +1. File and line or nearest symbol +2. Category: reuse, quality, efficiency, or clarity +3. Why it is a problem +4. Recommended fix +5. Confidence: high, medium, or low + +Discard weak, duplicative, or instruction-conflicting findings before editing. + +## Step 4: Fix Issues Carefully + +In `review-only` mode, stop after reporting findings. + +In `safe-fixes` or `fix-and-validate` mode: + +- Apply only high-confidence, behavior-preserving fixes +- Skip subjective refactors that need product or architectural judgment +- Preserve local patterns when they are intentional or instruction-backed +- Keep edits scoped to the reviewed files unless a small adjacent change is required to complete the fix correctly + +Prefer fixes like: + +- replacing duplicated code with an existing helper +- removing redundant state or dead code +- simplifying control flow without changing behavior +- narrowing overly broad operations +- renaming unclear locals when the scope is contained + +Do not stage, commit, or push changes as part of this skill. + +## Step 5: Validate When Required + +In `fix-and-validate` mode, run the smallest relevant validation for the touched scope after edits. + +Examples: + +- targeted tests for the touched module +- typecheck or compile for the touched target +- formatter or lint check if that is the project's real safety gate + +Prefer fast, scoped validation over full-suite runs unless the change breadth justifies more. + +If validation is skipped because the user asked not to run it, say so explicitly. + +## Step 6: Summarize Outcome + +Close with a brief result: + +- what was reviewed +- what was fixed, if anything +- what was intentionally left alone +- whether validation ran + +If the code is already clean for this rubric, say that directly instead of manufacturing edits. diff --git a/skills/snowflake-development/SKILL.md b/skills/snowflake-development/SKILL.md index 71a00fb8..a909e1c8 100644 --- a/skills/snowflake-development/SKILL.md +++ b/skills/snowflake-development/SKILL.md @@ -11,6 +11,11 @@ date_added: "2026-03-24" You are a Snowflake development expert. Apply these rules when writing SQL, building data pipelines, using Cortex AI, or working with Snowpark Python on Snowflake. +## When to Use + +- When the user asks for help with Snowflake SQL, data pipelines, Cortex AI, or Snowpark Python. +- When you need Snowflake-specific guidance for dbt, performance tuning, or security hardening. + ## SQL Best Practices ### Naming and Style diff --git a/skills/swift-concurrency-expert/SKILL.md b/skills/swift-concurrency-expert/SKILL.md new file mode 100644 index 00000000..1a9e5ab3 --- /dev/null +++ b/skills/swift-concurrency-expert/SKILL.md @@ -0,0 +1,113 @@ +--- +name: swift-concurrency-expert +description: Review and fix Swift concurrency issues such as actor isolation and Sendable violations. +risk: safe +source: "Dimillian/Skills (MIT)" +date_added: "2026-03-25" +--- + +# Swift Concurrency Expert + +## Overview + +Review and fix Swift Concurrency issues in Swift 6.2+ codebases by applying actor isolation, Sendable safety, and modern concurrency patterns with minimal behavior changes. + +## When to Use + +- When the user asks to review Swift concurrency usage or fix compiler diagnostics. +- When you need guidance on actor isolation, `Sendable`, `@MainActor`, or async migration. + +## Workflow + +### 1. Triage the issue + +- Capture the exact compiler diagnostics and the offending symbol(s). +- Check project concurrency settings: Swift language version (6.2+), strict concurrency level, and whether approachable concurrency (default actor isolation / main-actor-by-default) is enabled. +- Identify the current actor context (`@MainActor`, `actor`, `nonisolated`) and whether a default actor isolation mode is enabled. +- Confirm whether the code is UI-bound or intended to run off the main actor. + +### 2. Apply the smallest safe fix + +Prefer edits that preserve existing behavior while satisfying data-race safety. + +Common fixes: +- **UI-bound types**: annotate the type or relevant members with `@MainActor`. +- **Protocol conformance on main actor types**: make the conformance isolated (e.g., `extension Foo: @MainActor SomeProtocol`). +- **Global/static state**: protect with `@MainActor` or move into an actor. +- **Background work**: move expensive work into a `@concurrent` async function on a `nonisolated` type or use an `actor` to guard mutable state. +- **Sendable errors**: prefer immutable/value types; add `Sendable` conformance only when correct; avoid `@unchecked Sendable` unless you can prove thread safety. + +### 3. Verify the fix + +- Rebuild and confirm all concurrency diagnostics are resolved with no new warnings introduced. +- Run the test suite to check for regressions — concurrency changes can introduce subtle runtime issues even when the build is clean. +- If the fix surfaces new warnings, treat each one as a fresh triage (return to step 1) and resolve iteratively until the build is clean and tests pass. + +### Examples + +**UI-bound type — adding `@MainActor`** + +```swift +// Before: data-race warning because ViewModel is accessed from the main thread +// but has no actor isolation +class ViewModel: ObservableObject { + @Published var title: String = "" + func load() { title = "Loaded" } +} + +// After: annotate the whole type so all stored state and methods are +// automatically isolated to the main actor +@MainActor +class ViewModel: ObservableObject { + @Published var title: String = "" + func load() { title = "Loaded" } +} +``` + +**Protocol conformance isolation** + +```swift +// Before: compiler error — SomeProtocol method is nonisolated but the +// conforming type is @MainActor +@MainActor +class Foo: SomeProtocol { + func protocolMethod() { /* accesses main-actor state */ } +} + +// After: scope the conformance to @MainActor so the requirement is +// satisfied inside the correct isolation context +@MainActor +extension Foo: SomeProtocol { + func protocolMethod() { /* safely accesses main-actor state */ } +} +``` + +**Background work with `@concurrent`** + +```swift +// Before: expensive computation blocks the main actor +@MainActor +func processData(_ input: [Int]) -> [Int] { + input.map { heavyTransform($0) } // runs on main thread +} + +// After: hop off the main actor for the heavy work, then return the result +// The caller awaits the result and stays on its own actor +nonisolated func processData(_ input: [Int]) async -> [Int] { + await Task.detached(priority: .userInitiated) { + input.map { heavyTransform($0) } + }.value +} + +// Or, using a @concurrent async function (Swift 6.2+): +@concurrent +func processData(_ input: [Int]) async -> [Int] { + input.map { heavyTransform($0) } +} +``` + +## Reference material + +- See `references/swift-6-2-concurrency.md` for Swift 6.2 changes, patterns, and examples. +- See `references/approachable-concurrency.md` when the project is opted into approachable concurrency mode. +- See `references/swiftui-concurrency-tour-wwdc.md` for SwiftUI-specific concurrency guidance. diff --git a/skills/swift-concurrency-expert/agents/openai.yaml b/skills/swift-concurrency-expert/agents/openai.yaml new file mode 100644 index 00000000..28fb04e8 --- /dev/null +++ b/skills/swift-concurrency-expert/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Swift Concurrency Expert" + short_description: "Review and fix Swift concurrency" + default_prompt: "Use $swift-concurrency-expert to review this Swift code for concurrency issues and fix the relevant diagnostics." diff --git a/skills/swift-concurrency-expert/references/approachable-concurrency.md b/skills/swift-concurrency-expert/references/approachable-concurrency.md new file mode 100644 index 00000000..b495d139 --- /dev/null +++ b/skills/swift-concurrency-expert/references/approachable-concurrency.md @@ -0,0 +1,63 @@ +## Approachable Concurrency (Swift 6.2) - project mode quick guide + +Use this reference when the project has opted into the Swift 6.2 approachable +concurrency settings (default actor isolation / main-actor-by-default). + +## Detect the mode + +Check Xcode build settings under "Swift Compiler - Concurrency": +- Swift language version (must be 6.2+). +- Default actor isolation / Main Actor by default. +- Strict concurrency checking level (Complete/Targeted/Minimal). + +For SwiftPM, inspect Package.swift swiftSettings for the same flags. + +## Behavior changes to expect + +- Async functions stay on the caller's actor by default; they don't hop to a + global concurrent executor unless the implementation chooses to. +- Main-actor-by-default reduces data race errors for UI-bound code and global + state, because mutable state is implicitly protected. +- Protocol conformances can be isolated (e.g., `extension Foo: @MainActor Bar`). + +## How to apply fixes in this mode + +- Prefer minimal annotations; let main-actor-by-default do the work when the + code is UI-bound. +- Use isolated conformances instead of forcing `nonisolated` workarounds. +- Keep global or shared mutable state on the main actor unless there is a clear + performance need to offload it. + +## When to opt out or offload work + +- Use `@concurrent` on async functions that must run on the concurrent pool. +- Make types or members `nonisolated` only when they are truly thread-safe and + used off the main actor. +- Continue to respect Sendable boundaries when values cross actors or tasks. + +## Common pitfalls + +- `Task.detached` ignores inherited actor context; avoid it unless you truly + need to break isolation. +- Main-actor-by-default can hide performance issues if CPU-heavy work stays on + the main actor; move that work into `@concurrent` async functions. + +## Keywords (from source cheat sheet) + +| Keyword | What it does | +| --- | --- | +| `async` | Function can pause | +| `await` | Pause here until done | +| `Task { }` | Start async work, inherits context | +| `Task.detached { }` | Start async work, no inherited context | +| `@MainActor` | Runs on main thread | +| `actor` | Type with isolated mutable state | +| `nonisolated` | Opts out of actor isolation | +| `Sendable` | Safe to pass between isolation domains | +| `@concurrent` | Always run on background (Swift 6.2+) | +| `async let` | Start parallel work | +| `TaskGroup` | Dynamic parallel work | + +## Source + +https://fuckingapproachableswiftconcurrency.com/en/ diff --git a/skills/swift-concurrency-expert/references/swift-6-2-concurrency.md b/skills/swift-concurrency-expert/references/swift-6-2-concurrency.md new file mode 100644 index 00000000..aa4c5cb5 --- /dev/null +++ b/skills/swift-concurrency-expert/references/swift-6-2-concurrency.md @@ -0,0 +1,272 @@ +## Concurrent programming updates in Swift 6.2 + +Concurrent programming is hard because sharing memory between multiple tasks is prone to mistakes that lead to unpredictable behavior. + +## Data-race safety + + Data-race safety in Swift 6 prevents these mistakes at compile time, so you can write concurrent code without fear of introducing hard-to-debug runtime bugs. But in many cases, the most natural code to write is prone to data races, leading to compiler errors that you have to address. A class with mutable state, like this `PhotoProcessor` class, is safe as long as you don’t access it concurrently. + +```swift +class PhotoProcessor { + func extractSticker(data: Data, with id: String?) async -> Sticker? { } +} + +@MainActor +final class StickerModel { + let photoProcessor = PhotoProcessor() + + func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? { + guard let data = try await item.loadTransferable(type: Data.self) else { + return nil + } + + // Error: Sending 'self.photoProcessor' risks causing data races + // Sending main actor-isolated 'self.photoProcessor' to nonisolated instance method 'extractSticker(data:with:)' + // risks causing data races between nonisolated and main actor-isolated uses + return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier) + } +} +``` + + It has an async method to extract a `Sticker` by computing the subject of the given image data. But if you try to call `extractSticker` from UI code on the main actor, you’ll get an error that the call risks causing data races. This is because there are several places in the language that offload work to the background implicitly, even if you never needed code to run in parallel. + +Swift 6.2 changes this philosophy to stay single threaded by default until you choose to introduce concurrency. + +```swift +class PhotoProcessor { + func extractSticker(data: Data, with id: String?) async -> Sticker? { } +} + +@MainActor +final class StickerModel { + let photoProcessor = PhotoProcessor() + + func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? { + guard let data = try await item.loadTransferable(type: Data.self) else { + return nil + } + + // No longer a data race error in Swift 6.2 because of Approachable Concurrency and default actor isolation + return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier) + } +} +``` + +The language changes in Swift 6.2 make the most natural code to write data race free by default. This provides a more approachable path to introducing concurrency in a project. + +When you choose to introduce concurrency because you want to run code in parallel, data-race safety will protect you. + +First, we've made it easier to call async functions on types with mutable state. Instead of eagerly offloading async functions that aren't tied to a specific actor, the function will continue to run on the actor it was called from. This eliminates data races because the values passed into the async function are never sent outside the actor. Async functions can still offload work in their implementation, but clients don’t have to worry about their mutable state. + +Next, we’ve made it easier to implement conformances on main actor types. Here I have a protocol called `Exportable`, and I’m trying to implement a conformance for my main actor `StickerModel` class. The export requirement doesn’t have actor isolation, so the language assumed that it could be called from off the main actor, and prevented `StickerModel` from using main actor state in its implementation. + +```swift +protocol Exportable { + func export() +} + +extension StickerModel: Exportable { // error: Conformance of 'StickerModel' to protocol 'Exportable' crosses into main actor-isolated code and can cause data races + func export() { + photoProcessor.exportAsPNG() + } +} +``` + +Swift 6.2 supports these conformances. A conformance that needs main actor state is called an *isolated* conformance. This is safe because the compiler ensures a main actor conformance is only used on the main actor. + +```swift +// Isolated conformances + +protocol Exportable { + func export() +} + +extension StickerModel: @MainActor Exportable { + func export() { + photoProcessor.exportAsPNG() + } +} +``` + + I can create an `ImageExporter` type that adds a `StickerModel` to an array of any `Exportable` items as long as it stays on the main actor. + +```swift + // Isolated conformances + +@MainActor +struct ImageExporter { + var items: [any Exportable] + + mutating func add(_ item: StickerModel) { + items.append(item) + } + + func exportAll() { + for item in items { + item.export() + } + } +} +``` + +But if I allow `ImageExporter` to be used from anywhere, the compiler prevents adding `StickerModel` to the array because it isn’t safe to call export on `StickerModel` from outside the main actor. + +```swift +// Isolated conformances + +nonisolated +struct ImageExporter { + var items: [any Exportable] + + mutating func add(_ item: StickerModel) { + items.append(item) // error: Main actor-isolated conformance of 'StickerModel' to 'Exportable' cannot be used in nonisolated context + } + + func exportAll() { + for item in items { + item.export() + } + } +} +``` + +With isolated conformances, you only have to solve data race safety issues when the code indicates that it uses the conformance concurrently. + +## Global State + +Global and static variables are prone to data races because they allow mutable state to be accessed from anywhere. + +```swift +final class StickerLibrary { + static let shared: StickerLibrary = .init() // error: Static property 'shared' is not concurrency-safe because non-'Sendable' type 'StickerLibrary' may have shared mutable state +} +``` + +The most common way to protect global state is with the main actor. + +```swift +final class StickerLibrary { + @MainActor + static let shared: StickerLibrary = .init() +} +``` + + And it’s common to annotate an entire class with the main actor to protect all of its mutable state, especially in a project that doesn’t have a lot of concurrent tasks. + +```swift +@MainActor +final class StickerLibrary { + static let shared: StickerLibrary = .init() +} +``` + +You can model a program that's entirely single-threaded by writing `@MainActor` on everything in your project. + +```swift +@MainActor +final class StickerLibrary { + static let shared: StickerLibrary = .init() +} + +@MainActor +final class StickerModel { + let photoProcessor: PhotoProcessor + + var selection: [PhotosPickerItem] +} + +extension StickerModel: @MainActor Exportable { + func export() { + photoProcessor.exportAsPNG() + } +} +``` + +To make it easier to model single-threaded code, we’ve introduced a mode to infer main actor by default. + +```swift +// Mode to infer main actor by default in Swift 6.2 + +final class StickerLibrary { + static let shared: StickerLibrary = .init() +} + +final class StickerModel { + let photoProcessor: PhotoProcessor + + var selection: [PhotosPickerItem] +} + +extension StickerModel: Exportable { + func export() { + photoProcessor.exportAsPNG() + } +} +``` + + This eliminates data-race safety errors about unsafe global and static variables, calls to other main actor functions like ones from the SDK, and more, because the main actor protects all mutable state by default. It also reduces concurrency annotations in code that’s mostly single-threaded. This mode is great for projects that do most of the work on the main actor, and concurrent code is encapsulated within specific types or files. It’s opt-in and it’s recommended for apps, scripts, and other executable targets. + +## Offloading work to the background + +Offloading work to the background is still important for performance, such as keeping apps responsive when performing CPU-intensive tasks. + +Let’s look at the implementation of the `extractSticker` method on `PhotoProcessor`. + +```swift +// Explicitly offloading async work + +class PhotoProcessor { + var cachedStickers: [String: Sticker] + + func extractSticker(data: Data, with id: String) async -> Sticker { + if let sticker = cachedStickers[id] { + return sticker + } + + let sticker = await Self.extractSubject(from: data) + cachedStickers[id] = sticker + return sticker + } + + // Offload expensive image processing using the @concurrent attribute. + @concurrent + static func extractSubject(from data: Data) async -> Sticker { } +} +``` + +It first checks whether it already extracted a sticker for an image, so it can return the cached sticker immediately. If the sticker hasn’t been cached, it extracts the subject from the image data and creates a new sticker. The `extractSubject` method performs expensive image processing that I don’t want to block the main actor or any other actor. + +I can offload this work using the `@concurrent` attribute. `@concurrent` ensures that a function always runs on the concurrent thread pool, freeing up the actor to run other tasks at the same time. + +### An example + +Say you have a function called `process` that you would like to run on a background thread. To call that function on a background thread you need to: + +- make sure the structure or class is `nonisolated` +- add the `@concurrent` attribute to the function you want to run in the background +- add the keyword `async` to the function if it is not already asynchronous +- and then add the keyword `await` to any callers + +Like this: + +```swift +nonisolated struct PhotoProcessor { + + @concurrent + func process(data: Data) async -> ProcessedPhoto? { ... } +} + +// Callers with the added await +processedPhotos[item.id] = await PhotoProcessor().process(data: data) +``` + + +## Summary + +These language changes work together to make concurrency more approachable. + +You start by writing code that runs on the main actor by default, where there’s no risk of data races. When you start to use async functions, those functions run wherever they’re called from. There’s still no risk of data races because all of your code still runs on the main actor. When you’re ready to embrace concurrency to improve performance, it’s easy to offload specific code to the background to run in parallel. + +Some of these language changes are opt-in because they require changes in your project to adopt. You can find and enable all of the approachable concurrency language changes under the Swift Compiler - Concurrency section of Xcode build settings. You can also enable these features in a Swift package manifest file using the SwiftSettings API. + + Swift 6.2 includes migration tooling to help you make the necessary code changes automatically. You can learn more about migration tooling at swift.org/migration. diff --git a/skills/swift-concurrency-expert/references/swiftui-concurrency-tour-wwdc.md b/skills/swift-concurrency-expert/references/swiftui-concurrency-tour-wwdc.md new file mode 100644 index 00000000..ab248b25 --- /dev/null +++ b/skills/swift-concurrency-expert/references/swiftui-concurrency-tour-wwdc.md @@ -0,0 +1,33 @@ +# SwiftUI Concurrency Tour (Summary) + +Context: SwiftUI-focused concurrency overview covering actor isolation, Sendable closures, and how SwiftUI runs work off the main thread. + +## Main-actor default in SwiftUI + +- `View` is `@MainActor` isolated by default; members and `body` inherit isolation. +- Swift 6.2 can infer `@MainActor` for all types in a module (new language mode). +- This default simplifies UI code and aligns with UIKit/AppKit `@MainActor` APIs. + +## Where SwiftUI runs code off the main thread + +- SwiftUI may evaluate some view logic on background threads for performance. +- Examples: `Shape` path generation, `Layout` methods, `visualEffect` closures, and `onGeometryChange` closures. +- These APIs often require `Sendable` closures to reflect their runtime semantics. + +## Sendable closures and data-race safety + +- Accessing `@MainActor` state from a `Sendable` closure is unsafe and flagged by the compiler. +- Prefer capturing value copies in the closure capture list (e.g., copy a `Bool`). +- Avoid sending `self` into a sendable closure just to read a single property. + +## Structuring async work with SwiftUI + +- SwiftUI action callbacks are synchronous so UI updates (like loading states) can be immediate. +- Use `Task` to bridge into async contexts; keep async bodies minimal. +- Use state as the boundary: async work updates model/state; UI reacts synchronously. + +## Performance-driven concurrency + +- Offload expensive work from the main actor to avoid hitches. +- Keep time-sensitive UI logic (animations, gesture responses) synchronous. +- Separate UI code from long-running async work to improve responsiveness and testability. diff --git a/skills/swiftui-liquid-glass/SKILL.md b/skills/swiftui-liquid-glass/SKILL.md new file mode 100644 index 00000000..b37d88cf --- /dev/null +++ b/skills/swiftui-liquid-glass/SKILL.md @@ -0,0 +1,98 @@ +--- +name: swiftui-liquid-glass +description: Implement or review SwiftUI Liquid Glass APIs with correct fallbacks and modifier order. +risk: safe +source: "Dimillian/Skills (MIT)" +date_added: "2026-03-25" +--- + +# SwiftUI Liquid Glass + +## Overview +Use this skill to build or review SwiftUI features that fully align with the iOS 26+ Liquid Glass API. Prioritize native APIs (`glassEffect`, `GlassEffectContainer`, glass button styles) and Apple design guidance. Keep usage consistent, interactive where needed, and performance aware. + +## When to Use + +- When the user wants to adopt or review Liquid Glass in SwiftUI UI. +- When you need correct API usage, fallback handling, or modifier ordering for Liquid Glass. + +## Workflow Decision Tree +Choose the path that matches the request: + +### 1) Review an existing feature +- Inspect where Liquid Glass should be used and where it should not. +- Verify correct modifier order, shape usage, and container placement. +- Check for iOS 26+ availability handling and sensible fallbacks. + +### 2) Improve a feature using Liquid Glass +- Identify target components for glass treatment (surfaces, chips, buttons, cards). +- Refactor to use `GlassEffectContainer` where multiple glass elements appear. +- Introduce interactive glass only for tappable or focusable elements. + +### 3) Implement a new feature using Liquid Glass +- Design the glass surfaces and interactions first (shape, prominence, grouping). +- Add glass modifiers after layout/appearance modifiers. +- Add morphing transitions only when the view hierarchy changes with animation. + +## Core Guidelines +- Prefer native Liquid Glass APIs over custom blurs. +- Use `GlassEffectContainer` when multiple glass elements coexist. +- Apply `.glassEffect(...)` after layout and visual modifiers. +- Use `.interactive()` for elements that respond to touch/pointer. +- Keep shapes consistent across related elements for a cohesive look. +- Gate with `#available(iOS 26, *)` and provide a non-glass fallback. + +## Review Checklist +- **Availability**: `#available(iOS 26, *)` present with fallback UI. +- **Composition**: Multiple glass views wrapped in `GlassEffectContainer`. +- **Modifier order**: `glassEffect` applied after layout/appearance modifiers. +- **Interactivity**: `interactive()` only where user interaction exists. +- **Transitions**: `glassEffectID` used with `@Namespace` for morphing. +- **Consistency**: Shapes, tinting, and spacing align across the feature. + +## Implementation Checklist +- Define target elements and desired glass prominence. +- Wrap grouped glass elements in `GlassEffectContainer` and tune spacing. +- Use `.glassEffect(.regular.tint(...).interactive(), in: .rect(cornerRadius: ...))` as needed. +- Use `.buttonStyle(.glass)` / `.buttonStyle(.glassProminent)` for actions. +- Add morphing transitions with `glassEffectID` when hierarchy changes. +- Provide fallback materials and visuals for earlier iOS versions. + +## Quick Snippets +Use these patterns directly and tailor shapes/tints/spacing. + +```swift +if #available(iOS 26, *) { + Text("Hello") + .padding() + .glassEffect(.regular.interactive(), in: .rect(cornerRadius: 16)) +} else { + Text("Hello") + .padding() + .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 16)) +} +``` + +```swift +GlassEffectContainer(spacing: 24) { + HStack(spacing: 24) { + Image(systemName: "scribble.variable") + .frame(width: 72, height: 72) + .font(.system(size: 32)) + .glassEffect() + Image(systemName: "eraser.fill") + .frame(width: 72, height: 72) + .font(.system(size: 32)) + .glassEffect() + } +} +``` + +```swift +Button("Confirm") { } + .buttonStyle(.glassProminent) +``` + +## Resources +- Reference guide: `references/liquid-glass.md` +- Prefer Apple docs for up-to-date API details. diff --git a/skills/swiftui-liquid-glass/agents/openai.yaml b/skills/swiftui-liquid-glass/agents/openai.yaml new file mode 100644 index 00000000..29ab2a9f --- /dev/null +++ b/skills/swiftui-liquid-glass/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "SwiftUI Liquid Glass" + short_description: "Build SwiftUI Liquid Glass features" + default_prompt: "Use $swiftui-liquid-glass to implement or review a SwiftUI feature using Liquid Glass APIs." diff --git a/skills/swiftui-liquid-glass/references/liquid-glass.md b/skills/swiftui-liquid-glass/references/liquid-glass.md new file mode 100644 index 00000000..2abbd0d6 --- /dev/null +++ b/skills/swiftui-liquid-glass/references/liquid-glass.md @@ -0,0 +1,280 @@ +# Implementing Liquid Glass Design in SwiftUI + +## Overview + +Liquid Glass is a dynamic material introduced in iOS that combines the optical properties of glass with a sense of fluidity. It blurs content behind it, reflects color and light from surrounding content, and reacts to touch and pointer interactions in real time. This guide covers how to implement and customize Liquid Glass effects in SwiftUI applications. You should always seek guides on Liquid Glass when asked for help adopting new Apple design. + +Key features of Liquid Glass: +- Blurs content behind the material +- Reflects color and light from surrounding content +- Reacts to touch and pointer interactions +- Can morph between shapes during transitions +- Available for standard and custom components + +## Basic Implementation + +### Adding Liquid Glass to a View + +The simplest way to add Liquid Glass to a view is using the `glassEffect()` modifier: + +```swift +Text("Hello, World!") + .font(.title) + .padding() + .glassEffect() +``` + +By default, this applies the regular variant of Glass within a Capsule shape behind the view's content. + +### Customizing the Shape + +You can specify a different shape for the Liquid Glass effect: + +```swift +Text("Hello, World!") + .font(.title) + .padding() + .glassEffect(in: .rect(cornerRadius: 16.0)) +``` + +Common shape options: +- `.capsule` (default) +- `.rect(cornerRadius: CGFloat)` +- `.circle` + +## Customizing Liquid Glass Effects + +### Glass Variants and Properties + +You can customize the Liquid Glass effect by configuring the `Glass` structure: + +```swift +Text("Hello, World!") + .font(.title) + .padding() + .glassEffect(.regular.tint(.orange).interactive()) +``` + +Key customization options: +- `.regular` - Standard glass effect +- `.tint(Color)` - Add a color tint to suggest prominence +- `.interactive(Bool)` - Make the glass react to touch and pointer interactions + +### Making Interactive Glass + +To make Liquid Glass react to touch and pointer interactions: + +```swift +Text("Hello, World!") + .font(.title) + .padding() + .glassEffect(.regular.interactive(true)) +``` + +Or more concisely: + +```swift +Text("Hello, World!") + .font(.title) + .padding() + .glassEffect(.regular.interactive()) +``` + +## Working with Multiple Glass Effects + +### Using GlassEffectContainer + +When applying Liquid Glass effects to multiple views, use `GlassEffectContainer` for better rendering performance and to enable blending and morphing effects: + +```swift +GlassEffectContainer(spacing: 40.0) { + HStack(spacing: 40.0) { + Image(systemName: "scribble.variable") + .frame(width: 80.0, height: 80.0) + .font(.system(size: 36)) + .glassEffect() + + Image(systemName: "eraser.fill") + .frame(width: 80.0, height: 80.0) + .font(.system(size: 36)) + .glassEffect() + } +} +``` + +The `spacing` parameter controls how the Liquid Glass effects interact with each other: +- Smaller spacing: Views need to be closer to merge effects +- Larger spacing: Effects merge at greater distances + +### Uniting Multiple Glass Effects + +To combine multiple views into a single Liquid Glass effect, use the `glassEffectUnion` modifier: + +```swift +@Namespace private var namespace + +// Later in your view: +GlassEffectContainer(spacing: 20.0) { + HStack(spacing: 20.0) { + ForEach(symbolSet.indices, id: \.self) { item in + Image(systemName: symbolSet[item]) + .frame(width: 80.0, height: 80.0) + .font(.system(size: 36)) + .glassEffect() + .glassEffectUnion(id: item < 2 ? "1" : "2", namespace: namespace) + } + } +} +``` + +This is useful when creating views dynamically or with views that live outside of an HStack or VStack. + +## Morphing Effects and Transitions + +### Creating Morphing Transitions + +To create morphing effects during transitions between views with Liquid Glass: + +1. Create a namespace using the `@Namespace` property wrapper +2. Associate each Liquid Glass effect with a unique identifier using `glassEffectID` +3. Use animations when changing the view hierarchy + +```swift +@State private var isExpanded: Bool = false +@Namespace private var namespace + +var body: some View { + GlassEffectContainer(spacing: 40.0) { + HStack(spacing: 40.0) { + Image(systemName: "scribble.variable") + .frame(width: 80.0, height: 80.0) + .font(.system(size: 36)) + .glassEffect() + .glassEffectID("pencil", in: namespace) + + if isExpanded { + Image(systemName: "eraser.fill") + .frame(width: 80.0, height: 80.0) + .font(.system(size: 36)) + .glassEffect() + .glassEffectID("eraser", in: namespace) + } + } + } + + Button("Toggle") { + withAnimation { + isExpanded.toggle() + } + } + .buttonStyle(.glass) +} +``` + +The morphing effect occurs when views with Liquid Glass appear or disappear due to view hierarchy changes. + +## Button Styling with Liquid Glass + +### Glass Button Style + +SwiftUI provides built-in button styles for Liquid Glass: + +```swift +Button("Click Me") { + // Action +} +.buttonStyle(.glass) +``` + +### Glass Prominent Button Style + +For a more prominent glass button: + +```swift +Button("Important Action") { + // Action +} +.buttonStyle(.glassProminent) +``` + +## Advanced Techniques + +### Background Extension Effect + +To stretch content behind a sidebar or inspector with the background extension effect: + +```swift +NavigationSplitView { + // Sidebar content +} detail: { + // Detail content + .background { + // Background content that extends under the sidebar + } +} +``` + +### Extending Horizontal Scrolling Under Sidebar + +To extend horizontal scroll views under a sidebar or inspector: + +```swift +ScrollView(.horizontal) { + // Scrollable content +} +.scrollExtensionMode(.underSidebar) +``` + +## Best Practices + +1. **Container Usage**: Always use `GlassEffectContainer` when applying Liquid Glass to multiple views for better performance and morphing effects. + +2. **Effect Order**: Apply the `.glassEffect()` modifier after other modifiers that affect the appearance of the view. + +3. **Spacing Consideration**: Carefully choose spacing values in containers to control how and when glass effects merge. + +4. **Animation**: Use animations when changing view hierarchies to enable smooth morphing transitions. + +5. **Interactivity**: Add `.interactive()` to glass effects that should respond to user interaction. + +6. **Consistent Design**: Maintain consistent shapes and styles across your app for a cohesive look and feel. + +## Example: Custom Badge with Liquid Glass + +```swift +struct BadgeView: View { + let symbol: String + let color: Color + + var body: some View { + ZStack { + Image(systemName: "hexagon.fill") + .foregroundColor(color) + .font(.system(size: 50)) + + Image(systemName: symbol) + .foregroundColor(.white) + .font(.system(size: 30)) + } + .glassEffect(.regular, in: .rect(cornerRadius: 16)) + } +} + +// Usage: +GlassEffectContainer(spacing: 20) { + HStack(spacing: 20) { + BadgeView(symbol: "star.fill", color: .blue) + BadgeView(symbol: "heart.fill", color: .red) + BadgeView(symbol: "leaf.fill", color: .green) + } +} +``` + +## References + +- [Applying Liquid Glass to custom views](https://developer.apple.com/documentation/SwiftUI/Applying-Liquid-Glass-to-custom-views) +- [Landmarks: Building an app with Liquid Glass](https://developer.apple.com/documentation/SwiftUI/Landmarks-Building-an-app-with-Liquid-Glass) +- [SwiftUI View.glassEffect(_:in:isEnabled:)](https://developer.apple.com/documentation/SwiftUI/View/glassEffect(_:in:isEnabled:)) +- [SwiftUI GlassEffectContainer](https://developer.apple.com/documentation/SwiftUI/GlassEffectContainer) +- [SwiftUI GlassEffectTransition](https://developer.apple.com/documentation/SwiftUI/GlassEffectTransition) +- [SwiftUI GlassButtonStyle](https://developer.apple.com/documentation/SwiftUI/GlassButtonStyle) diff --git a/skills/swiftui-performance-audit/SKILL.md b/skills/swiftui-performance-audit/SKILL.md new file mode 100644 index 00000000..3e835952 --- /dev/null +++ b/skills/swiftui-performance-audit/SKILL.md @@ -0,0 +1,114 @@ +--- +name: swiftui-performance-audit +description: Audit SwiftUI performance issues from code review and profiling evidence. +risk: safe +source: "Dimillian/Skills (MIT)" +date_added: "2026-03-25" +--- + +# SwiftUI Performance Audit + +## Quick start + +Use this skill to diagnose SwiftUI performance issues from code first, then request profiling evidence when code review alone cannot explain the symptoms. + +## When to Use + +- When the user reports slow rendering, janky scrolling, layout thrash, or high CPU in SwiftUI. +- When you need a code-first audit plus Instruments guidance if profiling evidence is required. + +## Workflow + +1. Classify the symptom: slow rendering, janky scrolling, high CPU, memory growth, hangs, or excessive view updates. +2. If code is available, start with a code-first review using `references/code-smells.md`. +3. If code is not available, ask for the smallest useful slice: target view, data flow, reproduction steps, and deployment target. +4. If code review is inconclusive or runtime evidence is required, guide the user through profiling with `references/profiling-intake.md`. +5. Summarize likely causes, evidence, remediation, and validation steps using `references/report-template.md`. + +## 1. Intake + +Collect: +- Target view or feature code. +- Symptoms and exact reproduction steps. +- Data flow: `@State`, `@Binding`, environment dependencies, and observable models. +- Whether the issue shows up on device or simulator, and whether it was observed in Debug or Release. + +Ask the user to classify the issue if possible: +- CPU spike or battery drain +- Janky scrolling or dropped frames +- High memory or image pressure +- Hangs or unresponsive interactions +- Excessive or unexpectedly broad view updates + +For the full profiling intake checklist, read `references/profiling-intake.md`. + +## 2. Code-First Review + +Focus on: +- Invalidation storms from broad observation or environment reads. +- Unstable identity in lists and `ForEach`. +- Heavy derived work in `body` or view builders. +- Layout thrash from complex hierarchies, `GeometryReader`, or preference chains. +- Large image decode or resize work on the main thread. +- Animation or transition work applied too broadly. + +Use `references/code-smells.md` for the detailed smell catalog and fix guidance. + +Provide: +- Likely root causes with code references. +- Suggested fixes and refactors. +- If needed, a minimal repro or instrumentation suggestion. + +## 3. Guide the User to Profile + +If code review does not explain the issue, ask for runtime evidence: +- A trace export or screenshots of the SwiftUI timeline and Time Profiler call tree. +- Device/OS/build configuration. +- The exact interaction being profiled. +- Before/after metrics if the user is comparing a change. + +Use `references/profiling-intake.md` for the exact checklist and collection steps. + +## 4. Analyze and Diagnose + +- Map the evidence to the most likely category: invalidation, identity churn, layout thrash, main-thread work, image cost, or animation cost. +- Prioritize problems by impact, not by how easy they are to explain. +- Distinguish code-level suspicion from trace-backed evidence. +- Call out when profiling is still insufficient and what additional evidence would reduce uncertainty. + +## 5. Remediate + +Apply targeted fixes: +- Narrow state scope and reduce broad observation fan-out. +- Stabilize identities for `ForEach` and lists. +- Move heavy work out of `body` into derived state updated from inputs, model-layer precomputation, memoized helpers, or background preprocessing. Use `@State` only for view-owned state, not as an ad hoc cache for arbitrary computation. +- Use `equatable()` only when equality is cheaper than recomputing the subtree and the inputs are truly value-semantic. +- Downsample images before rendering. +- Reduce layout complexity or use fixed sizing where possible. + +Use `references/code-smells.md` for examples, Observation-specific fan-out guidance, and remediation patterns. + +## 6. Verify + +Ask the user to re-run the same capture and compare with baseline metrics. +Summarize the delta (CPU, frame drops, memory peak) if provided. + +## Outputs + +Provide: +- A short metrics table (before/after if available). +- Top issues (ordered by impact). +- Proposed fixes with estimated effort. + +Use `references/report-template.md` when formatting the final audit. + +## References + +- Profiling intake and collection checklist: `references/profiling-intake.md` +- Common code smells and remediation patterns: `references/code-smells.md` +- Audit output template: `references/report-template.md` +- Add Apple documentation and WWDC resources under `references/` as they are supplied by the user. +- Optimizing SwiftUI performance with Instruments: `references/optimizing-swiftui-performance-instruments.md` +- Understanding and improving SwiftUI performance: `references/understanding-improving-swiftui-performance.md` +- Understanding hangs in your app: `references/understanding-hangs-in-your-app.md` +- Demystify SwiftUI performance (WWDC23): `references/demystify-swiftui-performance-wwdc23.md` diff --git a/skills/swiftui-performance-audit/agents/openai.yaml b/skills/swiftui-performance-audit/agents/openai.yaml new file mode 100644 index 00000000..ed7978fd --- /dev/null +++ b/skills/swiftui-performance-audit/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "SwiftUI Performance Audit" + short_description: "Audit SwiftUI runtime performance" + default_prompt: "Use $swiftui-performance-audit to review this SwiftUI code for performance issues and suggest concrete fixes." diff --git a/skills/swiftui-performance-audit/references/code-smells.md b/skills/swiftui-performance-audit/references/code-smells.md new file mode 100644 index 00000000..8d5a7bb6 --- /dev/null +++ b/skills/swiftui-performance-audit/references/code-smells.md @@ -0,0 +1,150 @@ +# Common code smells and remediation patterns + +## Intent + +Use this reference during code-first review to map visible SwiftUI patterns to likely runtime costs and safer remediation guidance. + +## High-priority smells + +### Expensive formatters in `body` + +```swift +var body: some View { + let number = NumberFormatter() + let measure = MeasurementFormatter() + Text(measure.string(from: .init(value: meters, unit: .meters))) +} +``` + +Prefer cached formatters in a model or dedicated helper: + +```swift +final class DistanceFormatter { + static let shared = DistanceFormatter() + let number = NumberFormatter() + let measure = MeasurementFormatter() +} +``` + +### Heavy computed properties + +```swift +var filtered: [Item] { + items.filter { $0.isEnabled } +} +``` + +Prefer deriving this once per meaningful input change in a model/helper, or store derived view-owned state only when the view truly owns the transformation lifecycle. + +### Sorting or filtering inside `body` + +```swift +List { + ForEach(items.sorted(by: sortRule)) { item in + Row(item) + } +} +``` + +Prefer sorting before render work begins: + +```swift +let sortedItems = items.sorted(by: sortRule) +``` + +### Inline filtering inside `ForEach` + +```swift +ForEach(items.filter { $0.isEnabled }) { item in + Row(item) +} +``` + +Prefer a prefiltered collection with stable identity. + +### Unstable identity + +```swift +ForEach(items, id: \.self) { item in + Row(item) +} +``` + +Avoid `id: \.self` for non-stable values or collections that reorder. Use a stable domain identifier. + +### Top-level conditional view swapping + +```swift +var content: some View { + if isEditing { + editingView + } else { + readOnlyView + } +} +``` + +Prefer one stable base view and localize conditions to sections or modifiers. This reduces root identity churn and makes diffing cheaper. + +### Image decoding on the main thread + +```swift +Image(uiImage: UIImage(data: data)!) +``` + +Prefer decode and downsample work off the main thread, then store the processed image. + +## Observation fan-out + +### Broad `@Observable` reads on iOS 17+ + +```swift +@Observable final class Model { + var items: [Item] = [] +} + +var body: some View { + Row(isFavorite: model.items.contains(item)) +} +``` + +If many views read the same broad collection or root model, small changes can fan out into wide invalidation. Prefer narrower derived inputs, smaller observable surfaces, or per-item state closer to the leaf views. + +### Broad `ObservableObject` reads on iOS 16 and earlier + +```swift +final class Model: ObservableObject { + @Published var items: [Item] = [] +} +``` + +The same warning applies to legacy observation. Avoid having many descendants observe a large shared object when they only need one derived field. + +## Remediation notes + +### `@State` is not a generic cache + +Use `@State` for view-owned state and derived values that intentionally belong to the view lifecycle. Do not move arbitrary expensive computation into `@State` unless you also define when and why it updates. + +Better alternatives: +- precompute in the model or store +- update derived state in response to a specific input change +- memoize in a dedicated helper +- preprocess on a background task before rendering + +### `equatable()` is conditional guidance + +Use `equatable()` only when: +- equality is cheaper than recomputing the subtree, and +- the view inputs are value-semantic and stable enough for meaningful equality checks + +Do not apply `equatable()` as a blanket fix for all redraws. + +## Triage order + +When multiple smells appear together, prioritize in this order: +1. Broad invalidation and observation fan-out +2. Unstable identity and list churn +3. Main-thread work during render +4. Image decode or resize cost +5. Layout and animation complexity diff --git a/skills/swiftui-performance-audit/references/demystify-swiftui-performance-wwdc23.md b/skills/swiftui-performance-audit/references/demystify-swiftui-performance-wwdc23.md new file mode 100644 index 00000000..18de77c5 --- /dev/null +++ b/skills/swiftui-performance-audit/references/demystify-swiftui-performance-wwdc23.md @@ -0,0 +1,46 @@ +# Demystify SwiftUI Performance (WWDC23) (Summary) + +Context: WWDC23 session on building a mental model for SwiftUI performance and triaging hangs/hitches. + +## Performance loop + +- Measure -> Identify -> Optimize -> Re-measure. +- Focus on concrete symptoms (slow navigation, broken animations, spinning cursor). + +## Dependencies and updates + +- Views form a dependency graph; dynamic properties are a frequent source of updates. +- Use `Self._printChanges()` in debug only to inspect extra dependencies. +- Eliminate unnecessary dependencies by extracting views or narrowing state. +- Consider `@Observable` for more granular property tracking. + +## Common causes of slow updates + +- Expensive view bodies (string interpolation, filtering, formatting). +- Dynamic property instantiation and state initialization in `body`. +- Slow identity resolution in lists/tables. +- Hidden work: bundle lookups, heap allocations, repeated string construction. + +## Avoid slow initialization in view bodies + +- Don’t create heavy models synchronously in view bodies. +- Use `.task` to fetch async data and keep `init` lightweight. + +## Lists and tables identity rules + +- Stable identity is critical for performance and animation. +- Ensure a constant number of views per element in `ForEach`. +- Avoid inline filtering in `ForEach`; pre-filter and cache collections. +- Avoid `AnyView` in list rows; it hides identity and increases cost. +- Flatten nested `ForEach` when possible to reduce overhead. + +## Table specifics + +- `TableRow` resolves to a single row; row count must be constant. +- Prefer the streamlined `Table` initializer to enforce constant rows. +- Use explicit IDs for back deployment when needed. + +## Debugging aids + +- Use Instruments for hangs and hitches. +- Use `_printChanges` to validate dependency assumptions during debug. diff --git a/skills/swiftui-performance-audit/references/optimizing-swiftui-performance-instruments.md b/skills/swiftui-performance-audit/references/optimizing-swiftui-performance-instruments.md new file mode 100644 index 00000000..3080feb7 --- /dev/null +++ b/skills/swiftui-performance-audit/references/optimizing-swiftui-performance-instruments.md @@ -0,0 +1,29 @@ +# Optimizing SwiftUI Performance with Instruments (Summary) + +Context: WWDC session introducing the next-generation SwiftUI Instrument in Instruments 26 and how to diagnose SwiftUI-specific bottlenecks. + +## Key takeaways + +- Profile SwiftUI issues with the SwiftUI template (SwiftUI instrument + Time Profiler + Hangs/Hitches). +- Long view body updates are a common bottleneck; use "Long View Body Updates" to identify slow bodies. +- Set inspection range on a long update and correlate with Time Profiler to find expensive frames. +- Keep work out of `body`: move formatting, sorting, image decoding, and other expensive work into cached or precomputed paths. +- Use Cause & Effect Graph to diagnose *why* updates occur; SwiftUI is declarative, so backtraces are often unhelpful. +- Avoid broad dependencies that trigger many updates (e.g., `@Observable` arrays or global environment reads). +- Prefer granular view models and scoped state so only the affected view updates. +- Environment values update checks still cost time; avoid placing fast-changing values (timers, geometry) in environment. +- Profile early and often during feature development to catch regressions. + +## Suggested workflow (condensed) + +1. Record a trace in Release mode using the SwiftUI template. +2. Inspect "Long View Body Updates" and "Other Long Updates." +3. Zoom into a long update, then inspect Time Profiler for hot frames. +4. Fix slow body work by moving heavy logic into precomputed/cache paths. +5. Use Cause & Effect Graph to identify unintended update fan-out. +6. Re-record and compare the update counts and hitch frequency. + +## Example patterns from the session + +- Caching formatted distance strings in a location manager instead of computing in `body`. +- Replacing a dependency on a global favorites array with per-item view models to reduce update fan-out. diff --git a/skills/swiftui-performance-audit/references/profiling-intake.md b/skills/swiftui-performance-audit/references/profiling-intake.md new file mode 100644 index 00000000..39b6530d --- /dev/null +++ b/skills/swiftui-performance-audit/references/profiling-intake.md @@ -0,0 +1,44 @@ +# Profiling intake and collection checklist + +## Intent + +Use this checklist when code review alone cannot explain the SwiftUI performance issue and you need runtime evidence from the user. + +## Ask for first + +- Exact symptom: CPU spike, dropped frames, memory growth, hangs, or excessive view updates. +- Exact interaction: scrolling, typing, initial load, navigation push/pop, animation, sheet presentation, or background refresh. +- Target device and OS version. +- Whether the issue was reproduced on a real device or only in Simulator. +- Build configuration: Debug or Release. +- Whether the user already has a baseline or before/after comparison. + +## Default profiling request + +Ask the user to: +- Run the app in a Release build when possible. +- Use the SwiftUI Instruments template. +- Reproduce the exact problematic interaction only long enough to capture the issue. +- Capture the SwiftUI timeline and Time Profiler together. +- Export the trace or provide screenshots of the key SwiftUI lanes and the Time Profiler call tree. + +## Ask for these artifacts + +- Trace export or screenshots of the relevant SwiftUI lanes +- Time Profiler call tree screenshot or export +- Device/OS/build configuration +- A short note describing what action was happening at the time of the capture +- If memory is involved, the memory graph or Allocations data if available + +## When to ask for more + +- Ask for a second capture if the first run mixes multiple interactions. +- Ask for a before/after pair if the user has already tried a fix. +- Ask for a device capture if the issue only appears in Simulator or if scrolling smoothness matters. + +## Common traps + +- Debug builds can distort SwiftUI timing and allocation behavior. +- Simulator traces can miss device-only rendering or memory issues. +- Mixed interactions in one capture make attribution harder. +- Screenshots without the reproduction note are much harder to interpret. diff --git a/skills/swiftui-performance-audit/references/report-template.md b/skills/swiftui-performance-audit/references/report-template.md new file mode 100644 index 00000000..97982c7a --- /dev/null +++ b/skills/swiftui-performance-audit/references/report-template.md @@ -0,0 +1,47 @@ +# Audit output template + +## Intent + +Use this structure when reporting SwiftUI performance findings so the user can quickly see the symptom, evidence, likely cause, and next validation step. + +## Template + +```markdown +## Summary + +[One short paragraph on the most likely bottleneck and whether the conclusion is code-backed or trace-backed.] + +## Findings + +1. [Issue title] + - Symptom: [what the user sees] + - Likely cause: [root cause] + - Evidence: [code reference or profiling evidence] + - Fix: [specific change] + - Validation: [what to measure after the fix] + +2. [Issue title] + - Symptom: ... + - Likely cause: ... + - Evidence: ... + - Fix: ... + - Validation: ... + +## Metrics + +| Metric | Before | After | Notes | +| --- | --- | --- | --- | +| CPU | [value] | [value] | [note] | +| Frame drops / hitching | [value] | [value] | [note] | +| Memory peak | [value] | [value] | [note] | + +## Next step + +[One concrete next action: apply a fix, capture a better trace, or validate on device.] +``` + +## Notes + +- Order findings by impact, not by file order. +- Say explicitly when a conclusion is still a hypothesis. +- If no metrics are available, omit the table and say what should be measured next. diff --git a/skills/swiftui-performance-audit/references/understanding-hangs-in-your-app.md b/skills/swiftui-performance-audit/references/understanding-hangs-in-your-app.md new file mode 100644 index 00000000..2dab5e13 --- /dev/null +++ b/skills/swiftui-performance-audit/references/understanding-hangs-in-your-app.md @@ -0,0 +1,33 @@ +# Understanding Hangs in Your App (Summary) + +Context: Apple guidance on identifying hangs caused by long-running main-thread work and understanding the main run loop. + +## Key concepts + +- A hang is a noticeable delay in a discrete interaction (typically >100 ms). +- Hangs almost always come from long-running work on the main thread. +- The main run loop processes UI events, timers, and main-queue work sequentially. + +## Main-thread work stages + +- Event delivery to the correct view/handler. +- Your code: state updates, data fetch, UI changes. +- Core Animation commit to the render server. + +## Why the main run loop matters + +- Only the main thread can update UI safely. +- The run loop is the foundation that executes main-queue work. +- If the run loop is busy, it can’t handle new events; this causes hangs. + +## Diagnosing hangs + +- Observe the main run loop’s busy periods: healthy loops sleep most of the time. +- Hang detection typically flags busy periods >250 ms. +- The Hangs instrument can be configured to lower thresholds. + +## Practical takeaways + +- Keep main-thread work short; offload heavy work from event handlers. +- Avoid long-running tasks on the main dispatch queue or main actor. +- Use run loop behavior as a proxy for user-perceived responsiveness. diff --git a/skills/swiftui-performance-audit/references/understanding-improving-swiftui-performance.md b/skills/swiftui-performance-audit/references/understanding-improving-swiftui-performance.md new file mode 100644 index 00000000..2281f06b --- /dev/null +++ b/skills/swiftui-performance-audit/references/understanding-improving-swiftui-performance.md @@ -0,0 +1,52 @@ +# Understanding and Improving SwiftUI Performance (Summary) + +Context: Apple guidance on diagnosing SwiftUI performance with Instruments and applying design patterns to reduce long or frequent updates. + +## Core concepts + +- SwiftUI is declarative; view updates are driven by state, environment, and observable data dependencies. +- View bodies must compute quickly to meet frame deadlines; slow or frequent updates lead to hitches. +- Instruments is the primary tool to find long-running updates and excessive update frequency. + +## Instruments workflow + +1. Profile via Product > Profile. +2. Choose the SwiftUI template and record. +3. Exercise the target interaction. +4. Stop recording and inspect the SwiftUI track + Time Profiler. + +## SwiftUI timeline lanes + +- Update Groups: overview of time SwiftUI spends calculating updates. +- Long View Body Updates: orange >500us, red >1000us. +- Long Platform View Updates: AppKit/UIKit hosting in SwiftUI. +- Other Long Updates: geometry/text/layout and other SwiftUI work. +- Hitches: frame misses where UI wasn’t ready in time. + +## Diagnose long view body updates + +- Expand the SwiftUI track; inspect module-specific subtracks. +- Set Inspection Range and correlate with Time Profiler. +- Use call tree or flame graph to identify expensive frames. +- Repeat the update to gather enough samples for analysis. +- Filter to a specific update (Show Calls Made by `MySwiftUIView.body`). + +## Diagnose frequent updates + +- Use Update Groups to find long active groups without long updates. +- Set inspection range on the group and analyze update counts. +- Use Cause graph ("Show Causes") to see what triggers updates. +- Compare causes with expected data flow; prioritize the highest-frequency causes. + +## Remediation patterns + +- Move expensive work out of `body` and cache results. +- Use `Observable()` macro to scope dependencies to properties actually read. +- Avoid broad dependencies that fan out updates to many views. +- Reduce layout churn; isolate state-dependent subtrees from layout readers. +- Avoid storing closures that capture parent state; precompute child views. +- Gate frequent updates (e.g., geometry changes) by thresholds. + +## Verification + +- Re-record after changes to confirm reduced update counts and fewer hitches. diff --git a/skills/swiftui-ui-patterns/SKILL.md b/skills/swiftui-ui-patterns/SKILL.md new file mode 100644 index 00000000..7debb87d --- /dev/null +++ b/skills/swiftui-ui-patterns/SKILL.md @@ -0,0 +1,103 @@ +--- +name: swiftui-ui-patterns +description: Apply proven SwiftUI UI patterns for navigation, sheets, async state, and reusable screens. +risk: safe +source: "Dimillian/Skills (MIT)" +date_added: "2026-03-25" +--- + +# SwiftUI UI Patterns + +## Quick start + +## When to Use + +- When creating or refactoring SwiftUI screens, flows, or reusable UI components. +- When you need guidance on navigation, sheets, async state, previews, or component patterns. + +Choose a track based on your goal: + +### Existing project + +- Identify the feature or screen and the primary interaction model (list, detail, editor, settings, tabbed). +- Find a nearby example in the repo with `rg "TabView\("` or similar, then read the closest SwiftUI view. +- Apply local conventions: prefer SwiftUI-native state, keep state local when possible, and use environment injection for shared dependencies. +- Choose the relevant component reference from `references/components-index.md` and follow its guidance. +- If the interaction reveals secondary content by dragging or scrolling the primary content away, read `references/scroll-reveal.md` before implementing gestures manually. +- Build the view with small, focused subviews and SwiftUI-native data flow. + +### New project scaffolding + +- Start with `references/app-wiring.md` to wire TabView + NavigationStack + sheets. +- Add a minimal `AppTab` and `RouterPath` based on the provided skeletons. +- Choose the next component reference based on the UI you need first (TabView, NavigationStack, Sheets). +- Expand the route and sheet enums as new screens are added. + +## General rules to follow + +- Use modern SwiftUI state (`@State`, `@Binding`, `@Observable`, `@Environment`) and avoid unnecessary view models. +- If the deployment target includes iOS 16 or earlier and cannot use the Observation API introduced in iOS 17, fall back to `ObservableObject` with `@StateObject` for root ownership, `@ObservedObject` for injected observation, and `@EnvironmentObject` only for truly shared app-level state. +- Prefer composition; keep views small and focused. +- Use async/await with `.task` and explicit loading/error states. For restart, cancellation, and debouncing guidance, read `references/async-state.md`. +- Keep shared app services in `@Environment`, but prefer explicit initializer injection for feature-local dependencies and models. For root wiring patterns, read `references/app-wiring.md`. +- Prefer the newest SwiftUI API that fits the deployment target and call out the minimum OS whenever a pattern depends on it. +- Maintain existing legacy patterns only when editing legacy files. +- Follow the project's formatter and style guide. +- **Sheets**: Prefer `.sheet(item:)` over `.sheet(isPresented:)` when state represents a selected model. Avoid `if let` inside a sheet body. Sheets should own their actions and call `dismiss()` internally instead of forwarding `onCancel`/`onConfirm` closures. +- **Scroll-driven reveals**: Prefer deriving a normalized progress value from scroll offset and driving the visual state from that single source of truth. Avoid parallel gesture state machines unless scroll alone cannot express the interaction. + +## State ownership summary + +Use the narrowest state tool that matches the ownership model: + +| Scenario | Preferred pattern | +| --- | --- | +| Local UI state owned by one view | `@State` | +| Child mutates parent-owned value state | `@Binding` | +| Root-owned reference model on iOS 17+ | `@State` with an `@Observable` type | +| Child reads or mutates an injected `@Observable` model on iOS 17+ | Pass it explicitly as a stored property | +| Shared app service or configuration | `@Environment(Type.self)` | +| Legacy reference model on iOS 16 and earlier | `@StateObject` at the root, `@ObservedObject` when injected | + +Choose the ownership location first, then pick the wrapper. Do not introduce a reference model when plain value state is enough. + +## Cross-cutting references + +- `references/navigationstack.md`: navigation ownership, per-tab history, and enum routing. +- `references/sheets.md`: centralized modal presentation and enum-driven sheets. +- `references/deeplinks.md`: URL handling and routing external links into app destinations. +- `references/app-wiring.md`: root dependency graph, environment usage, and app shell wiring. +- `references/async-state.md`: `.task`, `.task(id:)`, cancellation, debouncing, and async UI state. +- `references/previews.md`: `#Preview`, fixtures, mock environments, and isolated preview setup. +- `references/performance.md`: stable identity, observation scope, lazy containers, and render-cost guardrails. + +## Anti-patterns + +- Giant views that mix layout, business logic, networking, routing, and formatting in one file. +- Multiple boolean flags for mutually exclusive sheets, alerts, or navigation destinations. +- Live service calls directly inside `body`-driven code paths instead of view lifecycle hooks or injected models/services. +- Reaching for `AnyView` to work around type mismatches that should be solved with better composition. +- Defaulting every shared dependency to `@EnvironmentObject` or a global router without a clear ownership reason. + +## Workflow for a new SwiftUI view + +1. Define the view's state, ownership location, and minimum OS assumptions before writing UI code. +2. Identify which dependencies belong in `@Environment` and which should stay as explicit initializer inputs. +3. Sketch the view hierarchy, routing model, and presentation points; extract repeated parts into subviews. For complex navigation, read `references/navigationstack.md`, `references/sheets.md`, or `references/deeplinks.md`. **Build and verify no compiler errors before proceeding.** +4. Implement async loading with `.task` or `.task(id:)`, plus explicit loading and error states when needed. Read `references/async-state.md` when the work depends on changing inputs or cancellation. +5. Add previews for the primary and secondary states, then add accessibility labels or identifiers when the UI is interactive. Read `references/previews.md` when the view needs fixtures or injected mock dependencies. +6. Validate with a build: confirm no compiler errors, check that previews render without crashing, ensure state changes propagate correctly, and sanity-check that list identity and observation scope will not cause avoidable re-renders. Read `references/performance.md` if the screen is large, scroll-heavy, or frequently updated. For common SwiftUI compilation errors — missing `@State` annotations, ambiguous `ViewBuilder` closures, or mismatched generic types — resolve them before updating callsites. **If the build fails:** read the error message carefully, fix the identified issue, then rebuild before proceeding to the next step. If a preview crashes, isolate the offending subview, confirm its state initialisation is valid, and re-run the preview before continuing. + +## Component references + +Use `references/components-index.md` as the entry point. Each component reference should include: +- Intent and best-fit scenarios. +- Minimal usage pattern with local conventions. +- Pitfalls and performance notes. +- Paths to existing examples in the current repo. + +## Adding a new component reference + +- Create `references/.md`. +- Keep it short and actionable; link to concrete files in the current repo. +- Update `references/components-index.md` with the new entry. diff --git a/skills/swiftui-ui-patterns/agents/openai.yaml b/skills/swiftui-ui-patterns/agents/openai.yaml new file mode 100644 index 00000000..5ddfa5d3 --- /dev/null +++ b/skills/swiftui-ui-patterns/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "SwiftUI UI Patterns" + short_description: "Apply practical SwiftUI UI patterns" + default_prompt: "Use $swiftui-ui-patterns to design or refactor this SwiftUI UI with strong default patterns." diff --git a/skills/swiftui-ui-patterns/references/app-wiring.md b/skills/swiftui-ui-patterns/references/app-wiring.md new file mode 100644 index 00000000..c70eabcd --- /dev/null +++ b/skills/swiftui-ui-patterns/references/app-wiring.md @@ -0,0 +1,201 @@ +# App wiring and dependency graph + +## Intent + +Show how to wire the app shell (TabView + NavigationStack + sheets) and install a global dependency graph (environment objects, services, streaming clients, SwiftData ModelContainer) in one place. + +## Recommended structure + +1) Root view sets up tabs, per-tab routers, and sheets. +2) A dedicated view modifier installs global dependencies and lifecycle tasks (auth state, streaming watchers, push tokens, data containers). +3) Feature views pull only what they need from the environment; feature-specific state stays local. + +## Dependency selection + +- Use `@Environment` for app-level services, shared clients, theme/configuration, and values that many descendants genuinely need. +- Prefer initializer injection for feature-local dependencies and models. Do not move a dependency into the environment just to avoid passing one or two arguments. +- Keep mutable feature state out of the environment unless it is intentionally shared across broad parts of the app. +- Use `@EnvironmentObject` only as a legacy fallback or when the project already standardizes on it for a truly shared object. + +## Root shell example (generic) + +```swift +@MainActor +struct AppView: View { + @State private var selectedTab: AppTab = .home + @State private var tabRouter = TabRouter() + + var body: some View { + TabView(selection: $selectedTab) { + ForEach(AppTab.allCases) { tab in + let router = tabRouter.router(for: tab) + NavigationStack(path: tabRouter.binding(for: tab)) { + tab.makeContentView() + } + .withSheetDestinations(sheet: Binding( + get: { router.presentedSheet }, + set: { router.presentedSheet = $0 } + )) + .environment(router) + .tabItem { tab.label } + .tag(tab) + } + } + .withAppDependencyGraph() + } +} +``` + +Minimal `AppTab` example: + +```swift +@MainActor +enum AppTab: Identifiable, Hashable, CaseIterable { + case home, notifications, settings + var id: String { String(describing: self) } + + @ViewBuilder + func makeContentView() -> some View { + switch self { + case .home: HomeView() + case .notifications: NotificationsView() + case .settings: SettingsView() + } + } + + @ViewBuilder + var label: some View { + switch self { + case .home: Label("Home", systemImage: "house") + case .notifications: Label("Notifications", systemImage: "bell") + case .settings: Label("Settings", systemImage: "gear") + } + } +} +``` + +Router skeleton: + +```swift +@MainActor +@Observable +final class RouterPath { + var path: [Route] = [] + var presentedSheet: SheetDestination? +} + +enum Route: Hashable { + case detail(id: String) +} +``` + +## Dependency graph modifier (generic) + +Use a single modifier to install environment objects and handle lifecycle hooks when the active account/client changes. This keeps wiring consistent and avoids forgetting a dependency in call sites. + +```swift +extension View { + func withAppDependencyGraph( + accountManager: AccountManager = .shared, + currentAccount: CurrentAccount = .shared, + currentInstance: CurrentInstance = .shared, + userPreferences: UserPreferences = .shared, + theme: Theme = .shared, + watcher: StreamWatcher = .shared, + pushNotifications: PushNotificationsService = .shared, + intentService: AppIntentService = .shared, + quickLook: QuickLook = .shared, + toastCenter: ToastCenter = .shared, + namespace: Namespace.ID? = nil, + isSupporter: Bool = false + ) -> some View { + environment(accountManager) + .environment(accountManager.currentClient) + .environment(quickLook) + .environment(currentAccount) + .environment(currentInstance) + .environment(userPreferences) + .environment(theme) + .environment(watcher) + .environment(pushNotifications) + .environment(intentService) + .environment(toastCenter) + .environment(\.isSupporter, isSupporter) + .task(id: accountManager.currentClient.id) { + let client = accountManager.currentClient + if let namespace { quickLook.namespace = namespace } + currentAccount.setClient(client: client) + currentInstance.setClient(client: client) + userPreferences.setClient(client: client) + await currentInstance.fetchCurrentInstance() + watcher.setClient(client: client, instanceStreamingURL: currentInstance.instance?.streamingURL) + if client.isAuth { + watcher.watch(streams: [.user, .direct]) + } else { + watcher.stopWatching() + } + } + .task(id: accountManager.pushAccounts.map(\.token)) { + pushNotifications.tokens = accountManager.pushAccounts.map(\.token) + } + } +} +``` + +Notes: +- The `.task(id:)` hooks respond to account/client changes, re-seeding services and watcher state. +- Keep the modifier focused on global wiring; feature-specific state stays within features. +- Adjust types (AccountManager, StreamWatcher, etc.) to match your project. + +## SwiftData / ModelContainer + +Install your `ModelContainer` at the root so all feature views share the same store. Keep the list minimal to the models that need persistence. + +```swift +extension View { + func withModelContainer() -> some View { + modelContainer(for: [Draft.self, LocalTimeline.self, TagGroup.self]) + } +} +``` + +Why: a single container avoids duplicated stores per sheet or tab and keeps data consistent. + +## Sheet routing (enum-driven) + +Centralize sheets with a small enum and a helper modifier. + +```swift +enum SheetDestination: Identifiable { + case composer + case settings + var id: String { String(describing: self) } +} + +extension View { + func withSheetDestinations(sheet: Binding) -> some View { + sheet(item: sheet) { destination in + switch destination { + case .composer: + ComposerView().withEnvironments() + case .settings: + SettingsView().withEnvironments() + } + } + } +} +``` + +Why: enum-driven sheets keep presentation centralized and testable; adding a new sheet means adding one enum case and one switch branch. + +## When to use + +- Apps with multiple packages/modules that share environment objects and services. +- Apps that need to react to account/client changes and rewire streaming/push safely. +- Any app that wants consistent TabView + NavigationStack + sheet wiring without repeating environment setup. + +## Caveats + +- Keep the dependency modifier slim; do not put feature state or heavy logic there. +- Ensure `.task(id:)` work is lightweight or cancelled appropriately; long-running work belongs in services. +- If unauthenticated clients exist, gate streaming/watch calls to avoid reconnect spam. diff --git a/skills/swiftui-ui-patterns/references/async-state.md b/skills/swiftui-ui-patterns/references/async-state.md new file mode 100644 index 00000000..4fe83137 --- /dev/null +++ b/skills/swiftui-ui-patterns/references/async-state.md @@ -0,0 +1,96 @@ +# Async state and task lifecycle + +## Intent + +Use this pattern when a view loads data, reacts to changing input, or coordinates async work that should follow the SwiftUI view lifecycle. + +## Core rules + +- Use `.task` for load-on-appear work that belongs to the view lifecycle. +- Use `.task(id:)` when async work should restart for a changing input such as a query, selection, or identifier. +- Treat cancellation as a normal path for view-driven tasks. Check `Task.isCancelled` in longer flows and avoid surfacing cancellation as a user-facing error. +- Debounce or coalesce user-driven async work such as search before it fans out into repeated requests. +- Keep UI-facing models and mutations main-actor-safe; do background work in services, then publish the result back to UI state. + +## Example: load on appear + +```swift +struct DetailView: View { + let id: String + @State private var state: LoadState = .idle + @Environment(ItemClient.self) private var client + + var body: some View { + content + .task { + await load() + } + } + + @ViewBuilder + private var content: some View { + switch state { + case .idle, .loading: + ProgressView() + case .loaded(let item): + ItemContent(item: item) + case .failed(let error): + ErrorView(error: error) + } + } + + private func load() async { + state = .loading + do { + state = .loaded(try await client.fetch(id: id)) + } catch is CancellationError { + return + } catch { + state = .failed(error) + } + } +} +``` + +## Example: restart on input change + +```swift +struct SearchView: View { + @State private var query = "" + @State private var results: [ResultItem] = [] + @Environment(SearchClient.self) private var client + + var body: some View { + List(results) { item in + Text(item.title) + } + .searchable(text: $query) + .task(id: query) { + try? await Task.sleep(for: .milliseconds(250)) + guard !Task.isCancelled, !query.isEmpty else { + results = [] + return + } + do { + results = try await client.search(query) + } catch is CancellationError { + return + } catch { + results = [] + } + } + } +} +``` + +## When to move work out of the view + +- If the async flow spans multiple screens or must survive view dismissal, move it into a service or model. +- If the view is mostly coordinating app-level lifecycle or account changes, wire it at the app shell in `app-wiring.md`. +- If retry, caching, or offline policy becomes complex, keep the policy in the client/service and leave the view with simple state transitions. + +## Pitfalls + +- Do not start network work directly from `body`. +- Do not ignore cancellation for searches, typeahead, or rapidly changing selections. +- Avoid storing derived async state in multiple places when one source of truth is enough. diff --git a/skills/swiftui-ui-patterns/references/components-index.md b/skills/swiftui-ui-patterns/references/components-index.md new file mode 100644 index 00000000..0156b4d1 --- /dev/null +++ b/skills/swiftui-ui-patterns/references/components-index.md @@ -0,0 +1,50 @@ +# Components Index + +Use this file to find component and cross-cutting guidance. Each entry lists when to use it. + +## Available components + +- TabView: `references/tabview.md` — Use when building a tab-based app or any tabbed feature set. +- NavigationStack: `references/navigationstack.md` — Use when you need push navigation and programmatic routing, especially per-tab history. +- Sheets and presentation: `references/sheets.md` — Use for local item-driven sheets, centralized modal routing, and sheet-specific action patterns. +- Form and Settings: `references/form.md` — Use for settings, grouped inputs, and structured data entry. +- macOS Settings: `references/macos-settings.md` — Use when building a macOS Settings window with SwiftUI's Settings scene. +- Split views and columns: `references/split-views.md` — Use for iPad/macOS multi-column layouts or custom secondary columns. +- List and Section: `references/list.md` — Use for feed-style content and settings rows. +- ScrollView and Lazy stacks: `references/scrollview.md` — Use for custom layouts, horizontal scrollers, or grids. +- Scroll-reveal detail surfaces: `references/scroll-reveal.md` — Use when a detail screen reveals secondary content or actions as the user scrolls or swipes between full-screen sections. +- Grids: `references/grids.md` — Use for icon pickers, media galleries, and tiled layouts. +- Theming and dynamic type: `references/theming.md` — Use for app-wide theme tokens, colors, and type scaling. +- Controls (toggles, pickers, sliders): `references/controls.md` — Use for settings controls and input selection. +- Input toolbar (bottom anchored): `references/input-toolbar.md` — Use for chat/composer screens with a sticky input bar. +- Top bar overlays (iOS 26+ and fallback): `references/top-bar.md` — Use for pinned selectors or pills above scroll content. +- Overlay and toasts: `references/overlay.md` — Use for transient UI like banners or toasts. +- Focus handling: `references/focus.md` — Use for chaining fields and keyboard focus management. +- Searchable: `references/searchable.md` — Use for native search UI with scopes and async results. +- Async images and media: `references/media.md` — Use for remote media, previews, and media viewers. +- Haptics: `references/haptics.md` — Use for tactile feedback tied to key actions. +- Matched transitions: `references/matched-transitions.md` — Use for smooth source-to-destination animations. +- Deep links and URL routing: `references/deeplinks.md` — Use for in-app navigation from URLs. +- Title menus: `references/title-menus.md` — Use for filter or context menus in the navigation title. +- Menu bar commands: `references/menu-bar.md` — Use when adding or customizing macOS/iPadOS menu bar commands. +- Loading & placeholders: `references/loading-placeholders.md` — Use for redacted skeletons, empty states, and loading UX. +- Lightweight clients: `references/lightweight-clients.md` — Use for small, closure-based API clients injected into stores. + +## Cross-cutting references + +- App wiring and dependency graph: `references/app-wiring.md` — Use to wire the app shell, install shared dependencies, and decide what belongs in the environment. +- Async state and task lifecycle: `references/async-state.md` — Use when a view loads data, reacts to changing input, or needs cancellation/debouncing guidance. +- Previews: `references/previews.md` — Use when adding `#Preview`, fixtures, mock environments, or isolated preview setup. +- Performance guardrails: `references/performance.md` — Use when a screen is large, scroll-heavy, frequently updated, or showing signs of avoidable re-renders. + +## Planned components (create files as needed) + +- Web content: create `references/webview.md` — Use for embedded web content or in-app browsing. +- Status composer patterns: create `references/composer.md` — Use for composition or editor workflows. +- Text input and validation: create `references/text-input.md` — Use for forms, validation, and text-heavy input. +- Design system usage: create `references/design-system.md` — Use when applying shared styling rules. + +## Adding entries + +- Add the component file and link it here with a short “when to use” description. +- Keep each component reference short and actionable. diff --git a/skills/swiftui-ui-patterns/references/controls.md b/skills/swiftui-ui-patterns/references/controls.md new file mode 100644 index 00000000..5a708483 --- /dev/null +++ b/skills/swiftui-ui-patterns/references/controls.md @@ -0,0 +1,57 @@ +# Controls (Toggle, Slider, Picker) + +## Intent + +Use native controls for settings and configuration screens, keeping labels accessible and state bindings clear. + +## Core patterns + +- Bind controls directly to `@State`, `@Binding`, or `@AppStorage`. +- Prefer `Toggle` for boolean preferences. +- Use `Slider` for numeric ranges and show the current value in a label. +- Use `Picker` for discrete choices; use `.pickerStyle(.segmented)` only for 2–4 options. +- Keep labels visible and descriptive; avoid embedding buttons inside controls. + +## Example: toggles with sections + +```swift +Form { + Section("Notifications") { + Toggle("Mentions", isOn: $preferences.notificationsMentionsEnabled) + Toggle("Follows", isOn: $preferences.notificationsFollowsEnabled) + Toggle("Boosts", isOn: $preferences.notificationsBoostsEnabled) + } +} +``` + +## Example: slider with value text + +```swift +Section("Font Size") { + Slider(value: $fontSizeScale, in: 0.5...1.5, step: 0.1) + Text("Scale: \(String(format: \"%.1f\", fontSizeScale))") + .font(.scaledBody) +} +``` + +## Example: picker for enums + +```swift +Picker("Default Visibility", selection: $visibility) { + ForEach(Visibility.allCases, id: \.self) { option in + Text(option.title).tag(option) + } +} +``` + +## Design choices to keep + +- Group related controls in a `Form` section. +- Use `.disabled(...)` to reflect locked or inherited settings. +- Use `Label` inside toggles to combine icon + text when it adds clarity. + +## Pitfalls + +- Avoid `.pickerStyle(.segmented)` for large sets; use menu or inline styles instead. +- Don’t hide labels for sliders; always show context. +- Avoid hard-coding colors for controls; use theme tint sparingly. diff --git a/skills/swiftui-ui-patterns/references/deeplinks.md b/skills/swiftui-ui-patterns/references/deeplinks.md new file mode 100644 index 00000000..6bb7d5cf --- /dev/null +++ b/skills/swiftui-ui-patterns/references/deeplinks.md @@ -0,0 +1,66 @@ +# Deep links and navigation + +## Intent + +Route external URLs into in-app destinations while falling back to system handling when needed. + +## Core patterns + +- Centralize URL handling in the router (`handle(url:)`, `handleDeepLink(url:)`). +- Inject an `OpenURLAction` handler that delegates to the router. +- Use `.onOpenURL` for app scheme links and convert them to web URLs if needed. +- Let the router decide whether to navigate or open externally. + +## Example: router entry points + +```swift +@MainActor +final class RouterPath { + var path: [Route] = [] + var urlHandler: ((URL) -> OpenURLAction.Result)? + + func handle(url: URL) -> OpenURLAction.Result { + if isInternal(url) { + navigate(to: .status(id: url.lastPathComponent)) + return .handled + } + return urlHandler?(url) ?? .systemAction + } + + func handleDeepLink(url: URL) -> OpenURLAction.Result { + // Resolve federated URLs, then navigate. + navigate(to: .status(id: url.lastPathComponent)) + return .handled + } +} +``` + +## Example: attach to a root view + +```swift +extension View { + func withLinkRouter(_ router: RouterPath) -> some View { + self + .environment( + \.openURL, + OpenURLAction { url in + router.handle(url: url) + } + ) + .onOpenURL { url in + router.handleDeepLink(url: url) + } + } +} +``` + +## Design choices to keep + +- Keep URL parsing and decision logic inside the router. +- Avoid handling deep links in multiple places; one entry point is enough. +- Always provide a fallback to `OpenURLAction` or `UIApplication.shared.open`. + +## Pitfalls + +- Don’t assume the URL is internal; validate first. +- Avoid blocking UI while resolving remote links; use `Task`. diff --git a/skills/swiftui-ui-patterns/references/focus.md b/skills/swiftui-ui-patterns/references/focus.md new file mode 100644 index 00000000..da31fafd --- /dev/null +++ b/skills/swiftui-ui-patterns/references/focus.md @@ -0,0 +1,90 @@ +# Focus handling and field chaining + +## Intent + +Use `@FocusState` to control keyboard focus, chain fields, and coordinate focus across complex forms. + +## Core patterns + +- Use an enum to represent focusable fields. +- Set initial focus in `onAppear`. +- Use `.onSubmit` to move focus to the next field. +- For dynamic lists of fields, use an enum with associated values (e.g., `.option(Int)`). + +## Example: single field focus + +```swift +struct AddServerView: View { + @State private var server = "" + @FocusState private var isServerFieldFocused: Bool + + var body: some View { + Form { + TextField("Server", text: $server) + .focused($isServerFieldFocused) + } + .onAppear { isServerFieldFocused = true } + } +} +``` + +## Example: chained focus with enum + +```swift +struct EditTagView: View { + enum FocusField { case title, symbol, newTag } + @FocusState private var focusedField: FocusField? + + var body: some View { + Form { + TextField("Title", text: $title) + .focused($focusedField, equals: .title) + .onSubmit { focusedField = .symbol } + + TextField("Symbol", text: $symbol) + .focused($focusedField, equals: .symbol) + .onSubmit { focusedField = .newTag } + } + .onAppear { focusedField = .title } + } +} +``` + +## Example: dynamic focus for variable fields + +```swift +struct PollView: View { + enum FocusField: Hashable { case option(Int) } + @FocusState private var focused: FocusField? + @State private var options: [String] = ["", ""] + @State private var currentIndex = 0 + + var body: some View { + ForEach(options.indices, id: \.self) { index in + TextField("Option \(index + 1)", text: $options[index]) + .focused($focused, equals: .option(index)) + .onSubmit { addOption(at: index) } + } + .onAppear { focused = .option(0) } + } + + private func addOption(at index: Int) { + options.append("") + currentIndex = index + 1 + DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { + focused = .option(currentIndex) + } + } +} +``` + +## Design choices to keep + +- Keep focus state local to the view that owns the fields. +- Use focus changes to drive UX (validation messages, helper UI). +- Pair with `.scrollDismissesKeyboard(...)` when using ScrollView/Form. + +## Pitfalls + +- Don’t store focus state in shared objects; it is view-local. +- Avoid aggressive focus changes during animation; delay if needed. diff --git a/skills/swiftui-ui-patterns/references/form.md b/skills/swiftui-ui-patterns/references/form.md new file mode 100644 index 00000000..e7de4d45 --- /dev/null +++ b/skills/swiftui-ui-patterns/references/form.md @@ -0,0 +1,97 @@ +# Form + +## Intent + +Use `Form` for structured settings, grouped inputs, and action rows. This pattern keeps layout, spacing, and accessibility consistent for data entry screens. + +## Core patterns + +- Wrap the form in a `NavigationStack` only when it is presented in a sheet or standalone view without an existing navigation context. +- Group related controls into `Section` blocks. +- Use `.scrollContentBackground(.hidden)` plus a custom background color when you need design-system colors. +- Apply `.formStyle(.grouped)` for grouped styling when appropriate. +- Use `@FocusState` to manage keyboard focus in input-heavy forms. + +## Example: settings-style form + +```swift +@MainActor +struct SettingsView: View { + @Environment(Theme.self) private var theme + + var body: some View { + NavigationStack { + Form { + Section("General") { + NavigationLink("Display") { DisplaySettingsView() } + NavigationLink("Haptics") { HapticsSettingsView() } + } + + Section("Account") { + Button("Edit profile") { /* open sheet */ } + .buttonStyle(.plain) + } + .listRowBackground(theme.primaryBackgroundColor) + } + .navigationTitle("Settings") + .navigationBarTitleDisplayMode(.inline) + .scrollContentBackground(.hidden) + .background(theme.secondaryBackgroundColor) + } + } +} +``` + +## Example: modal form with validation + +```swift +@MainActor +struct AddRemoteServerView: View { + @Environment(\.dismiss) private var dismiss + @Environment(Theme.self) private var theme + + @State private var server: String = "" + @State private var isValid = false + @FocusState private var isServerFieldFocused: Bool + + var body: some View { + NavigationStack { + Form { + TextField("Server URL", text: $server) + .keyboardType(.URL) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + .focused($isServerFieldFocused) + .listRowBackground(theme.primaryBackgroundColor) + + Button("Add") { + guard isValid else { return } + dismiss() + } + .disabled(!isValid) + .listRowBackground(theme.primaryBackgroundColor) + } + .formStyle(.grouped) + .navigationTitle("Add Server") + .navigationBarTitleDisplayMode(.inline) + .scrollContentBackground(.hidden) + .background(theme.secondaryBackgroundColor) + .scrollDismissesKeyboard(.immediately) + .toolbar { CancelToolbarItem() } + .onAppear { isServerFieldFocused = true } + } + } +} +``` + +## Design choices to keep + +- Prefer `Form` over custom stacks for settings and input screens. +- Keep rows tappable by using `.contentShape(Rectangle())` and `.buttonStyle(.plain)` on row buttons. +- Use list row backgrounds to keep section styling consistent with your theme. + +## Pitfalls + +- Avoid heavy custom layouts inside a `Form`; it can lead to spacing issues. +- If you need highly custom layouts, prefer `ScrollView` + `VStack`. +- Don’t mix multiple background strategies; pick either default Form styling or custom colors. diff --git a/skills/swiftui-ui-patterns/references/grids.md b/skills/swiftui-ui-patterns/references/grids.md new file mode 100644 index 00000000..4a822a82 --- /dev/null +++ b/skills/swiftui-ui-patterns/references/grids.md @@ -0,0 +1,71 @@ +# Grids + +## Intent + +Use `LazyVGrid` for icon pickers, media galleries, and dense visual selections where items align in columns. + +## Core patterns + +- Use `.adaptive` columns for layouts that should scale across device sizes. +- Use multiple `.flexible` columns when you want a fixed column count. +- Keep spacing consistent and small to avoid uneven gutters. +- Use `GeometryReader` inside grid cells when you need square thumbnails. + +## Example: adaptive icon grid + +```swift +let columns = [GridItem(.adaptive(minimum: 120, maximum: 1024))] + +LazyVGrid(columns: columns, spacing: 6) { + ForEach(icons) { icon in + Button { + select(icon) + } label: { + ZStack(alignment: .bottomTrailing) { + Image(icon.previewName) + .resizable() + .aspectRatio(contentMode: .fit) + .cornerRadius(6) + if icon.isSelected { + Image(systemName: "checkmark.seal.fill") + .padding(4) + .tint(.green) + } + } + } + .buttonStyle(.plain) + } +} +``` + +## Example: fixed 3-column media grid + +```swift +LazyVGrid( + columns: [ + .init(.flexible(minimum: 100), spacing: 4), + .init(.flexible(minimum: 100), spacing: 4), + .init(.flexible(minimum: 100), spacing: 4), + ], + spacing: 4 +) { + ForEach(items) { item in + GeometryReader { proxy in + ThumbnailView(item: item) + .frame(width: proxy.size.width, height: proxy.size.width) + } + .aspectRatio(1, contentMode: .fit) + } +} +``` + +## Design choices to keep + +- Use `LazyVGrid` for large collections; avoid non-lazy grids for big sets. +- Keep tap targets full-bleed using `.contentShape(Rectangle())` when needed. +- Prefer adaptive grids for settings pickers and flexible layouts. + +## Pitfalls + +- Avoid heavy overlays in every grid cell; it can be expensive. +- Don’t nest grids inside other grids without a clear reason. diff --git a/skills/swiftui-ui-patterns/references/haptics.md b/skills/swiftui-ui-patterns/references/haptics.md new file mode 100644 index 00000000..41942bea --- /dev/null +++ b/skills/swiftui-ui-patterns/references/haptics.md @@ -0,0 +1,71 @@ +# Haptics + +## Intent + +Use haptics sparingly to reinforce user actions (tab selection, refresh, success/error) and respect user preferences. + +## Core patterns + +- Centralize haptic triggers in a `HapticManager` or similar utility. +- Gate haptics behind user preferences and hardware support. +- Use distinct types for different UX moments (selection vs. notification vs. refresh). + +## Example: simple haptic manager + +```swift +@MainActor +final class HapticManager { + static let shared = HapticManager() + + enum HapticType { + case buttonPress + case tabSelection + case dataRefresh(intensity: CGFloat) + case notification(UINotificationFeedbackGenerator.FeedbackType) + } + + private let selectionGenerator = UISelectionFeedbackGenerator() + private let impactGenerator = UIImpactFeedbackGenerator(style: .heavy) + private let notificationGenerator = UINotificationFeedbackGenerator() + + private init() { selectionGenerator.prepare() } + + func fire(_ type: HapticType, isEnabled: Bool) { + guard isEnabled else { return } + switch type { + case .buttonPress: + impactGenerator.impactOccurred() + case .tabSelection: + selectionGenerator.selectionChanged() + case let .dataRefresh(intensity): + impactGenerator.impactOccurred(intensity: intensity) + case let .notification(style): + notificationGenerator.notificationOccurred(style) + } + } +} +``` + +## Example: usage + +```swift +Button("Save") { + HapticManager.shared.fire(.notification(.success), isEnabled: preferences.hapticsEnabled) +} + +TabView(selection: $selectedTab) { /* tabs */ } + .onChange(of: selectedTab) { _, _ in + HapticManager.shared.fire(.tabSelection, isEnabled: preferences.hapticTabSelectionEnabled) + } +``` + +## Design choices to keep + +- Haptics should be subtle and not fire on every tiny interaction. +- Respect user preferences (toggle to disable). +- Keep haptic triggers close to the user action, not deep in data layers. + +## Pitfalls + +- Avoid firing multiple haptics in quick succession. +- Do not assume haptics are available; check support. diff --git a/skills/swiftui-ui-patterns/references/input-toolbar.md b/skills/swiftui-ui-patterns/references/input-toolbar.md new file mode 100644 index 00000000..9506d3fa --- /dev/null +++ b/skills/swiftui-ui-patterns/references/input-toolbar.md @@ -0,0 +1,51 @@ +# Input toolbar (bottom anchored) + +## Intent + +Use a bottom-anchored input bar for chat, composer, or quick actions without fighting the keyboard. + +## Core patterns + +- Use `.safeAreaInset(edge: .bottom)` to anchor the toolbar above the keyboard. +- Keep the main content in a `ScrollView` or `List`. +- Drive focus with `@FocusState` and set initial focus when needed. +- Avoid embedding the input bar inside the scroll content; keep it separate. + +## Example: scroll view + bottom input + +```swift +@MainActor +struct ConversationView: View { + @FocusState private var isInputFocused: Bool + + var body: some View { + ScrollViewReader { _ in + ScrollView { + LazyVStack { + ForEach(messages) { message in + MessageRow(message: message) + } + } + .padding(.horizontal, .layoutPadding) + } + .safeAreaInset(edge: .bottom) { + InputBar(text: $draft) + .focused($isInputFocused) + } + .scrollDismissesKeyboard(.interactively) + .onAppear { isInputFocused = true } + } + } +} +``` + +## Design choices to keep + +- Keep the input bar visually separated from the scrollable content. +- Use `.scrollDismissesKeyboard(.interactively)` for chat-like screens. +- Ensure send actions are reachable via keyboard return or a clear button. + +## Pitfalls + +- Avoid placing the input view inside the scroll stack; it will jump with content. +- Avoid nested scroll views that fight for drag gestures. diff --git a/skills/swiftui-ui-patterns/references/lightweight-clients.md b/skills/swiftui-ui-patterns/references/lightweight-clients.md new file mode 100644 index 00000000..8313d4f5 --- /dev/null +++ b/skills/swiftui-ui-patterns/references/lightweight-clients.md @@ -0,0 +1,93 @@ +# Lightweight Clients (Closure-Based) + +Use this pattern to keep networking or service dependencies simple and testable without introducing a full view model or heavy DI framework. It works well for SwiftUI apps where you want a small, composable API surface that can be swapped in previews/tests. + +## Intent +- Provide a tiny "client" type made of async closures. +- Keep business logic in a store or feature layer, not the view. +- Enable easy stubbing in previews/tests. + +## Minimal shape +```swift +struct SomeClient { + var fetchItems: (_ limit: Int) async throws -> [Item] + var search: (_ query: String, _ limit: Int) async throws -> [Item] +} + +extension SomeClient { + static func live(baseURL: URL = URL(string: "https://example.com")!) -> SomeClient { + let session = URLSession.shared + return SomeClient( + fetchItems: { limit in + // build URL, call session, decode + }, + search: { query, limit in + // build URL, call session, decode + } + ) + } +} +``` + +## Usage pattern +```swift +@MainActor +@Observable final class ItemsStore { + enum LoadState { case idle, loading, loaded, failed(String) } + + var items: [Item] = [] + var state: LoadState = .idle + private let client: SomeClient + + init(client: SomeClient) { + self.client = client + } + + func load(limit: Int = 20) async { + state = .loading + do { + items = try await client.fetchItems(limit) + state = .loaded + } catch { + state = .failed(error.localizedDescription) + } + } +} +``` + +```swift +struct ContentView: View { + @Environment(ItemsStore.self) private var store + + var body: some View { + List(store.items) { item in + Text(item.title) + } + .task { await store.load() } + } +} +``` + +```swift +@main +struct MyApp: App { + @State private var store = ItemsStore(client: .live()) + + var body: some Scene { + WindowGroup { + ContentView() + .environment(store) + } + } +} +``` + +## Guidance +- Keep decoding and URL-building in the client; keep state changes in the store. +- Make the store accept the client in `init` and keep it private. +- Avoid global singletons; use `.environment` for store injection. +- If you need multiple variants (mock/stub), add `static func mock(...)`. + +## Pitfalls +- Don’t put UI state in the client; keep state in the store. +- Don’t capture `self` or view state in the client closures. diff --git a/skills/swiftui-ui-patterns/references/list.md b/skills/swiftui-ui-patterns/references/list.md new file mode 100644 index 00000000..7b570faf --- /dev/null +++ b/skills/swiftui-ui-patterns/references/list.md @@ -0,0 +1,86 @@ +# List and Section + +## Intent + +Use `List` for feed-style content and settings-style rows where built-in row reuse, selection, and accessibility matter. + +## Core patterns + +- Prefer `List` for long, vertically scrolling content with repeated rows. +- Use `Section` headers to group related rows. +- Pair with `ScrollViewReader` when you need scroll-to-top or jump-to-id. +- Use `.listStyle(.plain)` for modern feed layouts. +- Use `.listStyle(.grouped)` for multi-section discovery/search pages where section grouping helps. +- Apply `.scrollContentBackground(.hidden)` + a custom background when you need a themed surface. +- Use `.listRowInsets(...)` and `.listRowSeparator(.hidden)` to tune row spacing and separators. +- Use `.environment(\\.defaultMinListRowHeight, ...)` to control dense list layouts. + +## Example: feed list with scroll-to-top + +```swift +@MainActor +struct TimelineListView: View { + @Environment(\.selectedTabScrollToTop) private var selectedTabScrollToTop + @State private var scrollToId: String? + + var body: some View { + ScrollViewReader { proxy in + List { + ForEach(items) { item in + TimelineRow(item: item) + .id(item.id) + .listRowInsets(.init(top: 12, leading: 16, bottom: 6, trailing: 16)) + .listRowSeparator(.hidden) + } + } + .listStyle(.plain) + .environment(\\.defaultMinListRowHeight, 1) + .onChange(of: scrollToId) { _, newValue in + if let newValue { + proxy.scrollTo(newValue, anchor: .top) + scrollToId = nil + } + } + .onChange(of: selectedTabScrollToTop) { _, newValue in + if newValue == 0 { + withAnimation { + proxy.scrollTo(ScrollToView.Constants.scrollToTop, anchor: .top) + } + } + } + } + } +} +``` + +## Example: settings-style list + +```swift +@MainActor +struct SettingsView: View { + var body: some View { + List { + Section("General") { + NavigationLink("Display") { DisplaySettingsView() } + NavigationLink("Haptics") { HapticsSettingsView() } + } + Section("Account") { + Button("Sign Out", role: .destructive) {} + } + } + .listStyle(.insetGrouped) + } +} +``` + +## Design choices to keep + +- Use `List` for dynamic feeds, settings, and any UI where row semantics help. +- Use stable IDs for rows to keep animations and scroll positioning reliable. +- Prefer `.contentShape(Rectangle())` on rows that should be tappable end-to-end. +- Use `.refreshable` for pull-to-refresh feeds when the data source supports it. + +## Pitfalls + +- Avoid heavy custom layouts inside a `List` row; use `ScrollView` + `LazyVStack` instead. +- Be careful mixing `List` and nested `ScrollView`; it can cause gesture conflicts. diff --git a/skills/swiftui-ui-patterns/references/loading-placeholders.md b/skills/swiftui-ui-patterns/references/loading-placeholders.md new file mode 100644 index 00000000..7014458b --- /dev/null +++ b/skills/swiftui-ui-patterns/references/loading-placeholders.md @@ -0,0 +1,38 @@ +# Loading & Placeholders + +Use this when a view needs a consistent loading state (skeletons, redaction, empty state) without blocking interaction. + +## Patterns to prefer + +- **Redacted placeholders** for list/detail content to preserve layout while loading. +- **ContentUnavailableView** for empty or error states after loading completes. +- **ProgressView** only for short, global operations (use sparingly in content-heavy screens). + +## Recommended approach + +1. Keep the real layout, render placeholder data, then apply `.redacted(reason: .placeholder)`. +2. For lists, show a fixed number of placeholder rows (avoid infinite spinners). +3. Switch to `ContentUnavailableView` when load finishes but data is empty. + +## Pitfalls + +- Don’t animate layout shifts during redaction; keep frames stable. +- Avoid nesting multiple spinners; use one loading indicator per section. +- Keep placeholder count small (3–6) to reduce jank on low-end devices. + +## Minimal usage + +```swift +VStack { + if isLoading { + ForEach(0..<3, id: \.self) { _ in + RowView(model: .placeholder()) + } + .redacted(reason: .placeholder) + } else if items.isEmpty { + ContentUnavailableView("No items", systemImage: "tray") + } else { + ForEach(items) { item in RowView(model: item) } + } +} +``` diff --git a/skills/swiftui-ui-patterns/references/macos-settings.md b/skills/swiftui-ui-patterns/references/macos-settings.md new file mode 100644 index 00000000..242c6b56 --- /dev/null +++ b/skills/swiftui-ui-patterns/references/macos-settings.md @@ -0,0 +1,71 @@ +# macOS Settings + +## Intent + +Use this when building a macOS Settings window backed by SwiftUI's `Settings` scene. + +## Core patterns + +- Declare the Settings scene in the `App` and compile it only for macOS. +- Keep settings content in a dedicated root view (`SettingsView`) and drive values with `@AppStorage`. +- Use `TabView` to group settings sections when you have more than one category. +- Use `Form` inside each tab to keep controls aligned and accessible. +- Use `OpenSettingsAction` or `SettingsLink` for in-app entry points to the Settings window. + +## Example: settings scene + +```swift +@main +struct MyApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + #if os(macOS) + Settings { + SettingsView() + } + #endif + } +} +``` + +## Example: tabbed settings view + +```swift +@MainActor +struct SettingsView: View { + @AppStorage("showPreviews") private var showPreviews = true + @AppStorage("fontSize") private var fontSize = 12.0 + + var body: some View { + TabView { + Form { + Toggle("Show Previews", isOn: $showPreviews) + Slider(value: $fontSize, in: 9...96) { + Text("Font Size (\(fontSize, specifier: "%.0f") pts)") + } + } + .tabItem { Label("General", systemImage: "gear") } + + Form { + Toggle("Enable Advanced Mode", isOn: .constant(false)) + } + .tabItem { Label("Advanced", systemImage: "star") } + } + .scenePadding() + .frame(maxWidth: 420, minHeight: 240) + } +} +``` + +## Skip navigation + +- Avoid wrapping `SettingsView` in a `NavigationStack` unless you truly need deep push navigation. +- Prefer tabs or sections; Settings is already presented as a separate window and should feel flat. +- If you must show hierarchical settings, use a single `NavigationSplitView` with a sidebar list of categories. + +## Pitfalls + +- Don’t reuse iOS-only settings layouts (full-screen stacks, toolbar-heavy flows). +- Avoid large custom view hierarchies inside `Form`; keep rows focused and accessible. diff --git a/skills/swiftui-ui-patterns/references/matched-transitions.md b/skills/swiftui-ui-patterns/references/matched-transitions.md new file mode 100644 index 00000000..94922dbd --- /dev/null +++ b/skills/swiftui-ui-patterns/references/matched-transitions.md @@ -0,0 +1,59 @@ +# Matched transitions + +## Intent + +Use matched transitions to create smooth continuity between a source view (thumbnail, avatar) and a destination view (sheet, detail, viewer). + +## Core patterns + +- Use a shared `Namespace` and a stable ID for the source. +- Use `matchedTransitionSource` + `navigationTransition(.zoom(...))` on iOS 26+. +- Use `matchedGeometryEffect` for in-place transitions within a view hierarchy. +- Keep IDs stable across view updates (avoid random UUIDs). + +## Example: media preview to full-screen viewer (iOS 26+) + +```swift +struct MediaPreview: View { + @Namespace private var namespace + @State private var selected: MediaAttachment? + + var body: some View { + ThumbnailView() + .matchedTransitionSource(id: selected?.id ?? "", in: namespace) + .sheet(item: $selected) { item in + MediaViewer(item: item) + .navigationTransition(.zoom(sourceID: item.id, in: namespace)) + } + } +} +``` + +## Example: matched geometry within a view + +```swift +struct ToggleBadge: View { + @Namespace private var space + @State private var isOn = false + + var body: some View { + Button { + withAnimation(.spring) { isOn.toggle() } + } label: { + Image(systemName: isOn ? "eye" : "eye.slash") + .matchedGeometryEffect(id: "icon", in: space) + } + } +} +``` + +## Design choices to keep + +- Prefer `matchedTransitionSource` for cross-screen transitions. +- Keep source and destination sizes reasonable to avoid jarring scale changes. +- Use `withAnimation` for state-driven transitions. + +## Pitfalls + +- Don’t use unstable IDs; it breaks the transition. +- Avoid mismatched shapes (e.g., square to circle) unless the design expects it. diff --git a/skills/swiftui-ui-patterns/references/media.md b/skills/swiftui-ui-patterns/references/media.md new file mode 100644 index 00000000..2201e05a --- /dev/null +++ b/skills/swiftui-ui-patterns/references/media.md @@ -0,0 +1,73 @@ +# Media (images, video, viewer) + +## Intent + +Use consistent patterns for loading images, previewing media, and presenting a full-screen viewer. + +## Core patterns + +- Use `LazyImage` (or `AsyncImage`) for remote images with loading states. +- Prefer a lightweight preview component for inline media. +- Use a shared viewer state (e.g., `QuickLook`) to present a full-screen media viewer. +- Use `openWindow` for desktop/visionOS and a sheet for iOS. + +## Example: inline media preview + +```swift +struct MediaPreviewRow: View { + @Environment(QuickLook.self) private var quickLook + + let attachments: [MediaAttachment] + + var body: some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack { + ForEach(attachments) { attachment in + LazyImage(url: attachment.previewURL) { state in + if let image = state.image { + image.resizable().aspectRatio(contentMode: .fill) + } else { + ProgressView() + } + } + .frame(width: 120, height: 120) + .clipped() + .onTapGesture { + quickLook.prepareFor( + selectedMediaAttachment: attachment, + mediaAttachments: attachments + ) + } + } + } + } + } +} +``` + +## Example: global media viewer sheet + +```swift +struct AppRoot: View { + @State private var quickLook = QuickLook.shared + + var body: some View { + content + .environment(quickLook) + .sheet(item: $quickLook.selectedMediaAttachment) { selected in + MediaUIView(selectedAttachment: selected, attachments: quickLook.mediaAttachments) + } + } +} +``` + +## Design choices to keep + +- Keep previews lightweight; load full media in the viewer. +- Use shared viewer state so any view can open media without prop-drilling. +- Use a single entry point for the viewer (sheet/window) to avoid duplicates. + +## Pitfalls + +- Avoid loading full-size images in list rows; use resized previews. +- Don’t present multiple viewer sheets at once; keep a single source of truth. diff --git a/skills/swiftui-ui-patterns/references/menu-bar.md b/skills/swiftui-ui-patterns/references/menu-bar.md new file mode 100644 index 00000000..6d274faa --- /dev/null +++ b/skills/swiftui-ui-patterns/references/menu-bar.md @@ -0,0 +1,101 @@ +# Menu Bar + +## Intent + +Use this when adding or customizing the macOS/iPadOS menu bar with SwiftUI commands. + +## Core patterns + +- Add commands at the `Scene` level with `.commands { ... }`. +- Use `SidebarCommands()` when your UI includes a navigation sidebar. +- Use `CommandMenu` for app-specific menus and group related actions. +- Use `CommandGroup` to insert items before/after system groups or replace them. +- Use `FocusedValue` for context-sensitive menu items that depend on the active scene. + +## Example: basic command menu + +```swift +@main +struct MyApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + .commands { + CommandMenu("Actions") { + Button("Run", action: run) + .keyboardShortcut("R") + Button("Stop", action: stop) + .keyboardShortcut(".") + } + } + } + + private func run() {} + private func stop() {} +} +``` + +## Example: insert and replace groups + +```swift +WindowGroup { + ContentView() +} +.commands { + CommandGroup(before: .systemServices) { + Button("Check for Updates") { /* open updater */ } + } + + CommandGroup(after: .newItem) { + Button("New from Clipboard") { /* create item */ } + } + + CommandGroup(replacing: .help) { + Button("User Manual") { /* open docs */ } + } +} +``` + +## Example: focused menu state + +```swift +@Observable +final class DataModel { + var items: [String] = [] +} + +struct ContentView: View { + @State private var model = DataModel() + + var body: some View { + List(model.items, id: \.self) { item in + Text(item) + } + .focusedSceneValue(model) + } +} + +struct ItemCommands: Commands { + @FocusedValue(DataModel.self) private var model: DataModel? + + var body: some Commands { + CommandGroup(after: .newItem) { + Button("New Item") { + model?.items.append("Untitled") + } + .disabled(model == nil) + } + } +} +``` + +## Menu bar and Settings + +- Defining a `Settings` scene adds the Settings menu item on macOS automatically. +- If you need a custom entry point inside the app, use `OpenSettingsAction` or `SettingsLink`. + +## Pitfalls + +- Avoid registering the same keyboard shortcut in multiple command groups. +- Don’t use menu items as the only discoverable entry point for critical features. diff --git a/skills/swiftui-ui-patterns/references/navigationstack.md b/skills/swiftui-ui-patterns/references/navigationstack.md new file mode 100644 index 00000000..decf5c42 --- /dev/null +++ b/skills/swiftui-ui-patterns/references/navigationstack.md @@ -0,0 +1,159 @@ +# NavigationStack + +## Intent + +Use this pattern for programmatic navigation and deep links, especially when each tab needs an independent navigation history. The key idea is one `NavigationStack` per tab, each with its own path binding and router object. + +## Core architecture + +- Define a route enum that is `Hashable` and represents all destinations. +- Create a lightweight router (or use a library such as `https://github.com/Dimillian/AppRouter`) that owns the `path` and any sheet state. +- Each tab owns its own router instance and binds `NavigationStack(path:)` to it. +- Inject the router into the environment so child views can navigate programmatically. +- Centralize destination mapping with a single `navigationDestination(for:)` block (or a `withAppRouter()` modifier). + +## Example: custom router with per-tab stack + +```swift +@MainActor +@Observable +final class RouterPath { + var path: [Route] = [] + var presentedSheet: SheetDestination? + + func navigate(to route: Route) { + path.append(route) + } + + func reset() { + path = [] + } +} + +enum Route: Hashable { + case account(id: String) + case status(id: String) +} + +@MainActor +struct TimelineTab: View { + @State private var routerPath = RouterPath() + + var body: some View { + NavigationStack(path: $routerPath.path) { + TimelineView() + .navigationDestination(for: Route.self) { route in + switch route { + case .account(let id): AccountView(id: id) + case .status(let id): StatusView(id: id) + } + } + } + .environment(routerPath) + } +} +``` + +## Example: centralized destination mapping + +Use a shared view modifier to avoid duplicating route switches across screens. + +```swift +extension View { + func withAppRouter() -> some View { + navigationDestination(for: Route.self) { route in + switch route { + case .account(let id): + AccountView(id: id) + case .status(let id): + StatusView(id: id) + } + } + } +} +``` + +Then apply it once per stack: + +```swift +NavigationStack(path: $routerPath.path) { + TimelineView() + .withAppRouter() +} +``` + +## Example: binding per tab (tabs with independent history) + +```swift +@MainActor +struct TabsView: View { + @State private var timelineRouter = RouterPath() + @State private var notificationsRouter = RouterPath() + + var body: some View { + TabView { + TimelineTab(router: timelineRouter) + NotificationsTab(router: notificationsRouter) + } + } +} +``` + +## Example: generic tabs with per-tab NavigationStack + +Use this when tabs are built from data and each needs its own path without hard-coded names. + +```swift +@MainActor +struct TabsView: View { + @State private var selectedTab: AppTab = .timeline + @State private var tabRouter = TabRouter() + + var body: some View { + TabView(selection: $selectedTab) { + ForEach(AppTab.allCases) { tab in + NavigationStack(path: tabRouter.binding(for: tab)) { + tab.makeContentView() + } + .environment(tabRouter.router(for: tab)) + .tabItem { tab.label } + .tag(tab) + } + } + } +} +``` + +@MainActor +@Observable +final class TabRouter { + private var routers: [AppTab: RouterPath] = [:] + + func router(for tab: AppTab) -> RouterPath { + if let router = routers[tab] { return router } + let router = RouterPath() + routers[tab] = router + return router + } + + func binding(for tab: AppTab) -> Binding<[Route]> { + let router = router(for: tab) + return Binding(get: { router.path }, set: { router.path = $0 }) + } +} + +## Design choices to keep + +- One `NavigationStack` per tab to preserve independent history. +- A single source of truth for navigation state (`RouterPath` or library router). +- Use `navigationDestination(for:)` to map routes to views. +- Reset the path when app context changes (account switch, logout, etc.). +- Inject the router into the environment so child views can navigate and present sheets without prop-drilling. +- Keep sheet presentation state on the router if you want a single place to manage modals. + +## Pitfalls + +- Do not share one path across all tabs unless you want global history. +- Ensure route identifiers are stable and `Hashable`. +- Avoid storing view instances in the path; store lightweight route data instead. +- If using a router object, keep it outside other `@Observable` objects to avoid nested observation. diff --git a/skills/swiftui-ui-patterns/references/overlay.md b/skills/swiftui-ui-patterns/references/overlay.md new file mode 100644 index 00000000..a2a9a63f --- /dev/null +++ b/skills/swiftui-ui-patterns/references/overlay.md @@ -0,0 +1,45 @@ +# Overlay and toasts + +## Intent + +Use overlays for transient UI (toasts, banners, loaders) without affecting layout. + +## Core patterns + +- Use `.overlay(alignment:)` to place global UI without changing the underlying layout. +- Keep overlays lightweight and dismissible. +- Use a dedicated `ToastCenter` (or similar) for global state if multiple features trigger toasts. + +## Example: toast overlay + +```swift +struct AppRootView: View { + @State private var toast: Toast? + + var body: some View { + content + .overlay(alignment: .top) { + if let toast { + ToastView(toast: toast) + .transition(.move(edge: .top).combined(with: .opacity)) + .onAppear { + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + withAnimation { self.toast = nil } + } + } + } + } + } +} +``` + +## Design choices to keep + +- Prefer overlays for transient UI rather than embedding in layout stacks. +- Use transitions and short auto-dismiss timers. +- Keep the overlay aligned to a clear edge (`.top` or `.bottom`). + +## Pitfalls + +- Avoid overlays that block all interaction unless explicitly needed. +- Don’t stack many overlays; use a queue or replace the current toast. diff --git a/skills/swiftui-ui-patterns/references/performance.md b/skills/swiftui-ui-patterns/references/performance.md new file mode 100644 index 00000000..b574e73a --- /dev/null +++ b/skills/swiftui-ui-patterns/references/performance.md @@ -0,0 +1,62 @@ +# Performance guardrails + +## Intent + +Use these rules when a SwiftUI screen is large, scroll-heavy, frequently updated, or at risk of unnecessary recomputation. + +## Core rules + +- Give `ForEach` and list content stable identity. Do not use unstable indices as identity when the collection can reorder or mutate. +- Keep expensive filtering, sorting, and formatting out of `body`; precompute or move it into a model/helper when it is not trivial. +- Narrow observation scope so only the views that read changing state need to update. +- Prefer lazy containers for larger scrolling content and extract subviews when only part of a screen changes frequently. +- Avoid swapping entire top-level view trees for small state changes; keep a stable root view and vary localized sections or modifiers. + +## Example: stable identity + +```swift +ForEach(items) { item in + Row(item: item) +} +``` + +Prefer that over index-based identity when the collection can change order: + +```swift +ForEach(Array(items.enumerated()), id: \.offset) { _, item in + Row(item: item) +} +``` + +## Example: move expensive work out of body + +```swift +struct FeedView: View { + let items: [FeedItem] + + private var sortedItems: [FeedItem] { + items.sorted(using: KeyPathComparator(\.createdAt, order: .reverse)) + } + + var body: some View { + List(sortedItems) { item in + FeedRow(item: item) + } + } +} +``` + +If the work is more expensive than a small derived property, move it into a model, store, or helper that updates less often. + +## When to investigate further + +- Janky scrolling in long feeds or grids +- Typing lag from search or form validation +- Overly broad view updates when one small piece of state changes +- Large screens with many conditionals or repeated formatting work + +## Pitfalls + +- Recomputing heavy transforms every render +- Observing a large object from many descendants when only one field matters +- Building custom scroll containers when `List`, `LazyVStack`, or `LazyHGrid` would already solve the problem diff --git a/skills/swiftui-ui-patterns/references/previews.md b/skills/swiftui-ui-patterns/references/previews.md new file mode 100644 index 00000000..a07a3d8d --- /dev/null +++ b/skills/swiftui-ui-patterns/references/previews.md @@ -0,0 +1,48 @@ +# Previews + +## Intent + +Use previews to validate layout, state wiring, and injected dependencies without relying on a running app or live services. + +## Core rules + +- Add `#Preview` coverage for the primary state plus important secondary states such as loading, empty, and error. +- Use deterministic fixtures, mocks, and sample data. Do not make previews depend on live network calls, real databases, or global singletons. +- Install required environment dependencies directly in the preview so the view can render in isolation. +- Keep preview setup close to the view until it becomes noisy; then extract lightweight preview helpers or fixtures. +- If a preview crashes, fix the state initialization or dependency wiring before expanding the feature further. + +## Example: simple preview states + +```swift +#Preview("Loaded") { + ProfileView(profile: .fixture) +} + +#Preview("Empty") { + ProfileView(profile: nil) +} +``` + +## Example: preview with injected dependencies + +```swift +#Preview("Search results") { + SearchView() + .environment(SearchClient.preview(results: [.fixture, .fixture2])) + .environment(Theme.preview) +} +``` + +## Preview checklist + +- Does the preview install every required environment dependency? +- Does it cover at least one success path and one non-happy path? +- Are fixtures stable and small enough to be read quickly? +- Can the preview render without network, auth, or app-global initialization? + +## Pitfalls + +- Do not hide preview crashes by making dependencies optional if the production view requires them. +- Avoid huge inline fixtures when a named sample is easier to read. +- Do not couple previews to global shared singletons unless the project has no alternative. diff --git a/skills/swiftui-ui-patterns/references/scroll-reveal.md b/skills/swiftui-ui-patterns/references/scroll-reveal.md new file mode 100644 index 00000000..a278ece6 --- /dev/null +++ b/skills/swiftui-ui-patterns/references/scroll-reveal.md @@ -0,0 +1,133 @@ +# Scroll-reveal detail surfaces + +## Intent + +Use this pattern when a detail screen has a primary surface first and secondary content behind it, and you want the user to reveal that secondary layer by scrolling or swiping instead of tapping a separate button. + +Typical fits: + +- media detail screens that reveal actions or metadata +- maps, cards, or canvases that transition into structured detail +- full-screen viewers with a second "actions" or "insights" page + +## Core pattern + +Build the interaction as a paged vertical `ScrollView` with two sections: + +1. a primary section sized to the viewport +2. a secondary section below it + +Derive a normalized `progress` value from the vertical content offset and drive all visual changes from that one value. + +Avoid treating the reveal as a separate gesture system unless scroll alone cannot express it. + +## Minimal structure + +```swift +private enum DetailSection: Hashable { + case primary + case secondary +} + +struct DetailSurface: View { + @State private var revealProgress: CGFloat = 0 + @State private var secondaryHeight: CGFloat = 1 + + var body: some View { + GeometryReader { geometry in + ScrollViewReader { proxy in + ScrollView(.vertical, showsIndicators: false) { + VStack(spacing: 0) { + PrimaryContent(progress: revealProgress) + .frame(height: geometry.size.height) + .id(DetailSection.primary) + + SecondaryContent(progress: revealProgress) + .id(DetailSection.secondary) + .onGeometryChange(for: CGFloat.self) { geo in + geo.size.height + } action: { newHeight in + secondaryHeight = max(newHeight, 1) + } + } + .scrollTargetLayout() + } + .scrollTargetBehavior(.paging) + .onScrollGeometryChange(for: CGFloat.self, of: { scroll in + scroll.contentOffset.y + scroll.contentInsets.top + }) { _, offset in + revealProgress = (offset / secondaryHeight).clamped(to: 0...1) + } + .safeAreaInset(edge: .bottom) { + ChevronAffordance(progress: revealProgress) { + withAnimation(.smooth) { + let target: DetailSection = revealProgress < 0.5 ? .secondary : .primary + proxy.scrollTo(target, anchor: .top) + } + } + } + } + } + } +} +``` + +## Design choices to keep + +- Make the primary section exactly viewport-sized when the interaction should feel like paging between states. +- Compute `progress` from real scroll offset, not from duplicated booleans like `isExpanded`, `isShowingSecondary`, and `isSnapped`. +- Use `progress` to drive `offset`, `opacity`, `blur`, `scaleEffect`, and toolbar state so the whole surface stays synchronized. +- Use `ScrollViewReader` for programmatic snapping from taps on the primary content or chevron affordances. +- Use `onScrollTargetVisibilityChange` when you need a settled section state for haptics, tooltip dismissal, analytics, or accessibility announcements. + +## Morphing a shared control + +If a control appears to move from the primary surface into the secondary content, do not render two fully visible copies. + +Instead: + +- expose a source anchor in the primary area +- expose a destination anchor in the secondary area +- render one overlay that interpolates position and size using `progress` + +```swift +Color.clear + .anchorPreference(key: ControlAnchorKey.self, value: .bounds) { anchor in + ["source": anchor] + } + +Color.clear + .anchorPreference(key: ControlAnchorKey.self, value: .bounds) { anchor in + ["destination": anchor] + } + +.overlayPreferenceValue(ControlAnchorKey.self) { anchors in + MorphingControlOverlay(anchors: anchors, progress: revealProgress) +} +``` + +This keeps the motion coherent and avoids duplicate-hit-target bugs. + +## Haptics and affordances + +- Use light threshold haptics when the reveal begins and stronger haptics near the committed state. +- Keep a visible affordance like a chevron or pill while `progress` is near zero. +- Flip, fade, or blur the affordance as the secondary section becomes active. + +## Interaction guards + +- Disable vertical scrolling when a conflicting mode is active, such as pinch-to-zoom, crop, or full-screen media manipulation. +- Disable hit testing on overlays that should disappear once the secondary content is revealed. +- Avoid same-axis nested scroll views unless the inner view is effectively static or disabled during the reveal. + +## Pitfalls + +- Do not hard-code the progress divisor. Measure the secondary section height or another real reveal distance. +- Do not mix multiple animation sources for the same property. If `progress` drives it, keep other animations off that property. +- Do not store derived state like `isSecondaryVisible` unless another API requires it. Prefer deriving it from `progress` or visible scroll targets. +- Beware of layout feedback loops when measuring heights. Clamp zero values and update only when the measured height actually changes. + +## Concrete example + +- Pool iOS tile detail reveal: `/Users/dimillian/Documents/Dev/Pool/pool-ios/Pool/Sources/Features/Tile/Detail/TileDetailView.swift` +- Secondary content anchor example: `/Users/dimillian/Documents/Dev/Pool/pool-ios/Pool/Sources/Features/Tile/Detail/TileDetailIntentListView.swift` diff --git a/skills/swiftui-ui-patterns/references/scrollview.md b/skills/swiftui-ui-patterns/references/scrollview.md new file mode 100644 index 00000000..849c58cd --- /dev/null +++ b/skills/swiftui-ui-patterns/references/scrollview.md @@ -0,0 +1,87 @@ +# ScrollView and Lazy stacks + +## Intent + +Use `ScrollView` with `LazyVStack`, `LazyHStack`, or `LazyVGrid` when you need custom layout, mixed content, or horizontal/ grid-based scrolling. + +## Core patterns + +- Prefer `ScrollView` + `LazyVStack` for chat-like or custom feed layouts. +- Use `ScrollView(.horizontal)` + `LazyHStack` for chips, tags, avatars, and media strips. +- Use `LazyVGrid` for icon/media grids; prefer adaptive columns when possible. +- Use `ScrollViewReader` for scroll-to-top/bottom and anchor-based jumps. +- Use `safeAreaInset(edge:)` for input bars that should stick above the keyboard. + +## Example: vertical custom feed + +```swift +@MainActor +struct ConversationView: View { + private enum Constants { static let bottomAnchor = "bottom" } + @State private var scrollProxy: ScrollViewProxy? + + var body: some View { + ScrollViewReader { proxy in + ScrollView { + LazyVStack { + ForEach(messages) { message in + MessageRow(message: message) + .id(message.id) + } + Color.clear.frame(height: 1).id(Constants.bottomAnchor) + } + .padding(.horizontal, .layoutPadding) + } + .safeAreaInset(edge: .bottom) { + MessageInputBar() + } + .onAppear { + scrollProxy = proxy + withAnimation { + proxy.scrollTo(Constants.bottomAnchor, anchor: .bottom) + } + } + } + } +} +``` + +## Example: horizontal chips + +```swift +ScrollView(.horizontal, showsIndicators: false) { + LazyHStack(spacing: 8) { + ForEach(chips) { chip in + ChipView(chip: chip) + } + } +} +``` + +## Example: adaptive grid + +```swift +let columns = [GridItem(.adaptive(minimum: 120))] + +ScrollView { + LazyVGrid(columns: columns, spacing: 8) { + ForEach(items) { item in + GridItemView(item: item) + } + } + .padding(8) +} +``` + +## Design choices to keep + +- Use `Lazy*` stacks when item counts are large or unknown. +- Use non-lazy stacks for small, fixed-size content to avoid lazy overhead. +- Keep IDs stable when using `ScrollViewReader`. +- Prefer explicit animations (`withAnimation`) when scrolling to an ID. + +## Pitfalls + +- Avoid nesting scroll views of the same axis; it causes gesture conflicts. +- Don’t combine `List` and `ScrollView` in the same hierarchy without a clear reason. +- Overuse of `LazyVStack` for tiny content can add unnecessary complexity. diff --git a/skills/swiftui-ui-patterns/references/searchable.md b/skills/swiftui-ui-patterns/references/searchable.md new file mode 100644 index 00000000..6d7a2f15 --- /dev/null +++ b/skills/swiftui-ui-patterns/references/searchable.md @@ -0,0 +1,71 @@ +# Searchable + +## Intent + +Use `searchable` to add native search UI with optional scopes and async results. + +## Core patterns + +- Bind `searchable(text:)` to local state. +- Use `.searchScopes` for multiple search modes. +- Use `.task(id: searchQuery)` or debounced tasks to avoid overfetching. +- Show placeholders or progress states while results load. + +## Example: searchable with scopes + +```swift +@MainActor +struct ExploreView: View { + @State private var searchQuery = "" + @State private var searchScope: SearchScope = .all + @State private var isSearching = false + @State private var results: [SearchResult] = [] + + var body: some View { + List { + if isSearching { + ProgressView() + } else { + ForEach(results) { result in + SearchRow(result: result) + } + } + } + .searchable( + text: $searchQuery, + placement: .navigationBarDrawer(displayMode: .always), + prompt: Text("Search") + ) + .searchScopes($searchScope) { + ForEach(SearchScope.allCases, id: \.self) { scope in + Text(scope.title) + } + } + .task(id: searchQuery) { + await runSearch() + } + } + + private func runSearch() async { + guard !searchQuery.isEmpty else { + results = [] + return + } + isSearching = true + defer { isSearching = false } + try? await Task.sleep(for: .milliseconds(250)) + results = await fetchResults(query: searchQuery, scope: searchScope) + } +} +``` + +## Design choices to keep + +- Show a placeholder when search is empty or has no results. +- Debounce input to avoid spamming the network. +- Keep search state local to the view. + +## Pitfalls + +- Avoid running searches for empty strings. +- Don’t block the main thread during fetch. diff --git a/skills/swiftui-ui-patterns/references/sheets.md b/skills/swiftui-ui-patterns/references/sheets.md new file mode 100644 index 00000000..ce524122 --- /dev/null +++ b/skills/swiftui-ui-patterns/references/sheets.md @@ -0,0 +1,155 @@ +# Sheets + +## Intent + +Use a centralized sheet routing pattern so any view can present modals without prop-drilling. This keeps sheet state in one place and scales as the app grows. + +## Core architecture + +- Define a `SheetDestination` enum that describes every modal and is `Identifiable`. +- Store the current sheet in a router object (`presentedSheet: SheetDestination?`). +- Create a view modifier like `withSheetDestinations(...)` that maps the enum to concrete sheet views. +- Inject the router into the environment so child views can set `presentedSheet` directly. + +## Example: item-driven local sheet + +Use this when sheet state is local to one screen and does not need centralized routing. + +```swift +@State private var selectedItem: Item? + +.sheet(item: $selectedItem) { item in + EditItemSheet(item: item) +} +``` + +## Example: SheetDestination enum + +```swift +enum SheetDestination: Identifiable, Hashable { + case composer + case editProfile + case settings + case report(itemID: String) + + var id: String { + switch self { + case .composer, .editProfile: + // Use the same id to ensure only one editor-like sheet is active at a time. + return "editor" + case .settings: + return "settings" + case .report: + return "report" + } + } +} +``` + +## Example: withSheetDestinations modifier + +```swift +extension View { + func withSheetDestinations( + sheet: Binding + ) -> some View { + sheet(item: sheet) { destination in + Group { + switch destination { + case .composer: + ComposerView() + case .editProfile: + EditProfileView() + case .settings: + SettingsView() + case .report(let itemID): + ReportView(itemID: itemID) + } + } + } + } +} +``` + +## Example: presenting from a child view + +```swift +struct StatusRow: View { + @Environment(RouterPath.self) private var router + + var body: some View { + Button("Report") { + router.presentedSheet = .report(itemID: "123") + } + } +} +``` + +## Required wiring + +For the child view to work, a parent view must: +- own the router instance, +- attach `withSheetDestinations(sheet: $router.presentedSheet)` (or an equivalent `sheet(item:)` handler), and +- inject it with `.environment(router)` after the sheet modifier so the modal content inherits it. + +This makes the child assignment to `router.presentedSheet` drive presentation at the root. + +## Example: sheets that need their own navigation + +Wrap sheet content in a `NavigationStack` so it can push within the modal. + +```swift +struct NavigationSheet: View { + var content: () -> Content + + var body: some View { + NavigationStack { + content() + .toolbar { CloseToolbarItem() } + } + } +} +``` + +## Example: sheet owns its actions + +Keep dismissal and confirmation logic inside the sheet when the actions belong to the modal itself. + +```swift +struct EditItemSheet: View { + @Environment(\.dismiss) private var dismiss + @Environment(Store.self) private var store + + let item: Item + @State private var isSaving = false + + var body: some View { + VStack { + Button(isSaving ? "Saving..." : "Save") { + Task { await save() } + } + } + } + + private func save() async { + isSaving = true + await store.save(item) + dismiss() + } +} +``` + +## Design choices to keep + +- Centralize sheet routing so features can present modals without wiring bindings through many layers. +- Use `sheet(item:)` to guarantee a single sheet is active and to drive presentation from the enum. +- Group related sheets under the same `id` when they are mutually exclusive (e.g., editor flows). +- Keep sheet views lightweight and composed from smaller views; avoid large monoliths. +- Let sheets own their actions and call `dismiss()` internally instead of forwarding `onCancel` or `onConfirm` closures through many layers. + +## Pitfalls + +- Avoid mixing `sheet(isPresented:)` and `sheet(item:)` for the same concern; prefer a single enum. +- Avoid `if let` inside a sheet body when the presentation state already carries the selected model; prefer `sheet(item:)`. +- Do not store heavy state inside `SheetDestination`; pass lightweight identifiers or models. +- If multiple sheets can appear from the same screen, give them distinct `id` values. diff --git a/skills/swiftui-ui-patterns/references/split-views.md b/skills/swiftui-ui-patterns/references/split-views.md new file mode 100644 index 00000000..fe373497 --- /dev/null +++ b/skills/swiftui-ui-patterns/references/split-views.md @@ -0,0 +1,72 @@ +# Split views and columns + +## Intent + +Provide a lightweight, customizable multi-column layout for iPad/macOS without relying on `NavigationSplitView`. + +## Custom split column pattern (manual HStack) + +Use this when you want full control over column sizing, behavior, and environment tweaks. + +```swift +@MainActor +struct AppView: View { + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + @AppStorage("showSecondaryColumn") private var showSecondaryColumn = true + + var body: some View { + HStack(spacing: 0) { + primaryColumn + if shouldShowSecondaryColumn { + Divider().edgesIgnoringSafeArea(.all) + secondaryColumn + } + } + } + + private var shouldShowSecondaryColumn: Bool { + horizontalSizeClass == .regular + && showSecondaryColumn + } + + private var primaryColumn: some View { + TabView { /* tabs */ } + } + + private var secondaryColumn: some View { + NotificationsTab() + .environment(\.isSecondaryColumn, true) + .frame(maxWidth: .secondaryColumnWidth) + } +} +``` + +## Notes on the custom approach + +- Use a shared preference or setting to toggle the secondary column. +- Inject an environment flag (e.g., `isSecondaryColumn`) so child views can adapt behavior. +- Prefer a fixed or capped width for the secondary column to avoid layout thrash. + +## Alternative: NavigationSplitView + +`NavigationSplitView` can handle sidebar + detail + supplementary columns for you, but is harder to customize in cases like:\n- a dedicated notification column independent of selection,\n- custom sizing, or\n- different toolbar behaviors per column. + +```swift +@MainActor +struct AppView: View { + var body: some View { + NavigationSplitView { + SidebarView() + } content: { + MainContentView() + } detail: { + NotificationsView() + } + } +} +``` + +## When to choose which + +- Use the manual HStack split when you need full control or a non-standard secondary column. +- Use `NavigationSplitView` when you want a standard system layout with minimal customization. diff --git a/skills/swiftui-ui-patterns/references/tabview.md b/skills/swiftui-ui-patterns/references/tabview.md new file mode 100644 index 00000000..2a44bfe5 --- /dev/null +++ b/skills/swiftui-ui-patterns/references/tabview.md @@ -0,0 +1,114 @@ +# TabView + +## Intent + +Use this pattern for a scalable, multi-platform tab architecture with: +- a single source of truth for tab identity and content, +- platform-specific tab sets and sidebar sections, +- dynamic tabs sourced from data, +- an interception hook for special tabs (e.g., compose). + +## Core architecture + +- `AppTab` enum defines identity, labels, icons, and content builder. +- `SidebarSections` enum groups tabs for sidebar sections. +- `AppView` owns the `TabView` and selection binding, and routes tab changes through `updateTab`. + +## Example: custom binding with side effects + +Use this when tab selection needs side effects, like intercepting a special tab to perform an action instead of changing selection. + +```swift +@MainActor +struct AppView: View { + @Binding var selectedTab: AppTab + + var body: some View { + TabView(selection: .init( + get: { selectedTab }, + set: { updateTab(with: $0) } + )) { + ForEach(availableSections) { section in + TabSection(section.title) { + ForEach(section.tabs) { tab in + Tab(value: tab) { + tab.makeContentView( + homeTimeline: $timeline, + selectedTab: $selectedTab, + pinnedFilters: $pinnedFilters + ) + } label: { + tab.label + } + .tabPlacement(tab.tabPlacement) + } + } + .tabPlacement(.sidebarOnly) + } + } + } + + private func updateTab(with newTab: AppTab) { + if newTab == .post { + // Intercept special tabs (compose) instead of changing selection. + presentComposer() + return + } + selectedTab = newTab + } +} +``` + +## Example: direct binding without side effects + +Use this when selection is purely state-driven. + +```swift +@MainActor +struct AppView: View { + @Binding var selectedTab: AppTab + + var body: some View { + TabView(selection: $selectedTab) { + ForEach(availableSections) { section in + TabSection(section.title) { + ForEach(section.tabs) { tab in + Tab(value: tab) { + tab.makeContentView( + homeTimeline: $timeline, + selectedTab: $selectedTab, + pinnedFilters: $pinnedFilters + ) + } label: { + tab.label + } + .tabPlacement(tab.tabPlacement) + } + } + .tabPlacement(.sidebarOnly) + } + } + } +} +``` + +## Design choices to keep + +- Centralize tab identity and content in `AppTab` with `makeContentView(...)`. +- Use `Tab(value:)` with `selection` binding for state-driven tab selection. +- Route selection changes through `updateTab` to handle special tabs and scroll-to-top behavior. +- Use `TabSection` + `.tabPlacement(.sidebarOnly)` for sidebar structure. +- Use `.tabPlacement(.pinned)` in `AppTab.tabPlacement` for a single pinned tab; this is commonly used for iOS 26 `.searchable` tab content, but can be used for any tab. + +## Dynamic tabs pattern + +- `SidebarSections` handles dynamic data tabs. +- `AppTab.anyTimelineFilter(filter:)` wraps dynamic tabs in a single enum case. +- The enum provides label/icon/title for dynamic tabs via the filter type. + +## Pitfalls + +- Avoid adding ViewModels for tabs; keep state local or in `@Observable` services. +- Do not nest `@Observable` objects inside other `@Observable` objects. +- Ensure `AppTab.id` values are stable; dynamic cases should hash on stable IDs. +- Special tabs (compose) should not change selection. diff --git a/skills/swiftui-ui-patterns/references/theming.md b/skills/swiftui-ui-patterns/references/theming.md new file mode 100644 index 00000000..ec976fef --- /dev/null +++ b/skills/swiftui-ui-patterns/references/theming.md @@ -0,0 +1,71 @@ +# Theming and dynamic type + +## Intent + +Provide a clean, scalable theming approach that keeps view code semantic and consistent. + +## Core patterns + +- Use a single `Theme` object as the source of truth (colors, fonts, spacing). +- Inject theme at the app root and read it via `@Environment(Theme.self)` in views. +- Prefer semantic colors (`primaryBackground`, `secondaryBackground`, `label`, `tint`) instead of raw colors. +- Keep user-facing theme controls in a dedicated settings screen. +- Apply Dynamic Type scaling through custom fonts or `.font(.scaled...)`. + +## Example: Theme object + +```swift +@MainActor +@Observable +final class Theme { + var tintColor: Color = .blue + var primaryBackground: Color = .white + var secondaryBackground: Color = .gray.opacity(0.1) + var labelColor: Color = .primary + var fontSizeScale: Double = 1.0 +} +``` + +## Example: inject at app root + +```swift +@main +struct MyApp: App { + @State private var theme = Theme() + + var body: some Scene { + WindowGroup { + AppView() + .environment(theme) + } + } +} +``` + +## Example: view usage + +```swift +struct ProfileView: View { + @Environment(Theme.self) private var theme + + var body: some View { + VStack { + Text("Profile") + .foregroundStyle(theme.labelColor) + } + .background(theme.primaryBackground) + } +} +``` + +## Design choices to keep + +- Keep theme values semantic and minimal; avoid duplicating system colors. +- Store user-selected theme values in persistent storage if needed. +- Ensure contrast between text and backgrounds. + +## Pitfalls + +- Avoid sprinkling raw `Color` values in views; it breaks consistency. +- Do not tie theme to a single view’s local state. +- Avoid using `@Environment(\\.colorScheme)` as the only theme control; it should complement your theme. diff --git a/skills/swiftui-ui-patterns/references/title-menus.md b/skills/swiftui-ui-patterns/references/title-menus.md new file mode 100644 index 00000000..d77254dd --- /dev/null +++ b/skills/swiftui-ui-patterns/references/title-menus.md @@ -0,0 +1,93 @@ +# Title menus + +## Intent + +Use a title menu in the navigation bar to provide context‑specific filtering or quick actions without adding extra chrome. + +## Core patterns + +- Use `ToolbarTitleMenu` to attach a menu to the navigation title. +- Keep the menu content compact and grouped with dividers. + +## Example: title menu for filters + +```swift +@ToolbarContentBuilder +private var toolbarView: some ToolbarContent { + ToolbarTitleMenu { + Button("Latest") { timeline = .latest } + Button("Resume") { timeline = .resume } + Divider() + Button("Local") { timeline = .local } + Button("Federated") { timeline = .federated } + } +} +``` + +## Example: attach to a view + +```swift +NavigationStack { + TimelineView() + .toolbar { + toolbarView + } +} +``` + +## Example: title + menu together + +```swift +struct TimelineScreen: View { + @State private var timeline: TimelineFilter = .home + + var body: some View { + NavigationStack { + TimelineView() + .toolbar { + ToolbarItem(placement: .principal) { + VStack(spacing: 2) { + Text(timeline.title) + .font(.headline) + Text(timeline.subtitle) + .font(.caption) + .foregroundStyle(.secondary) + } + } + + ToolbarTitleMenu { + Button("Home") { timeline = .home } + Button("Local") { timeline = .local } + Button("Federated") { timeline = .federated } + } + } + .navigationBarTitleDisplayMode(.inline) + } + } +} +``` + +## Example: title + subtitle with menu + +```swift +ToolbarItem(placement: .principal) { + VStack(spacing: 2) { + Text(title) + .font(.headline) + Text(subtitle) + .font(.caption) + .foregroundStyle(.secondary) + } +} +``` + +## Design choices to keep + +- Only show the title menu when filtering or context switching is available. +- Keep the title readable; avoid long labels that truncate. +- Use secondary text below the title if extra context is needed. + +## Pitfalls + +- Don’t overload the menu with too many options. +- Avoid using title menus for destructive actions. diff --git a/skills/swiftui-ui-patterns/references/top-bar.md b/skills/swiftui-ui-patterns/references/top-bar.md new file mode 100644 index 00000000..f5ea8991 --- /dev/null +++ b/skills/swiftui-ui-patterns/references/top-bar.md @@ -0,0 +1,49 @@ +# Top bar overlays (iOS 26+ and fallback) + +## Intent + +Provide a custom top selector or pill row that sits above scroll content, using `safeAreaBar(.top)` on iOS 26 and a compatible fallback on earlier OS versions. + +## iOS 26+ approach + +Use `safeAreaBar(edge: .top)` to attach the view to the safe area bar. + +```swift +if #available(iOS 26.0, *) { + content + .safeAreaBar(edge: .top) { + TopSelectorView() + .padding(.horizontal, .layoutPadding) + } +} +``` + +## Fallback for earlier iOS + +Use `.safeAreaInset(edge: .top)` and hide the toolbar background to avoid double layers. + +```swift +content + .toolbarBackground(.hidden, for: .navigationBar) + .safeAreaInset(edge: .top, spacing: 0) { + VStack(spacing: 0) { + TopSelectorView() + .padding(.vertical, 8) + .padding(.horizontal, .layoutPadding) + .background(Color.primary.opacity(0.06)) + .background(Material.ultraThin) + Divider() + } + } +``` + +## Design choices to keep + +- Use `safeAreaBar` when available; it integrates better with the navigation bar. +- Use a subtle background + divider in the fallback to keep separation from content. +- Keep the selector height compact to avoid pushing content too far down. + +## Pitfalls + +- Don’t stack multiple top insets; it can create extra padding. +- Avoid heavy, opaque backgrounds that fight the navigation bar. diff --git a/skills/swiftui-view-refactor/SKILL.md b/skills/swiftui-view-refactor/SKILL.md new file mode 100644 index 00000000..8201c566 --- /dev/null +++ b/skills/swiftui-view-refactor/SKILL.md @@ -0,0 +1,210 @@ +--- +name: swiftui-view-refactor +description: Refactor SwiftUI views into smaller components with stable, explicit data flow. +risk: safe +source: "Dimillian/Skills (MIT)" +date_added: "2026-03-25" +--- + +# SwiftUI View Refactor + +## Overview +Refactor SwiftUI views toward small, explicit, stable view types. Default to vanilla SwiftUI: local state in the view, shared dependencies in the environment, business logic in services/models, and view models only when the request or existing code clearly requires one. + +## When to Use + +- When cleaning up a large SwiftUI view or splitting long `body` implementations. +- When you need smaller subviews, explicit dependency injection, or better Observation usage. + +## Core Guidelines + +### 1) View ordering (top → bottom) +- Enforce this ordering unless the existing file has a stronger local convention you must preserve. +- Environment +- `private`/`public` `let` +- `@State` / other stored properties +- computed `var` (non-view) +- `init` +- `body` +- computed view builders / other view helpers +- helper / async functions + +### 2) Default to MV, not MVVM +- Views should be lightweight state expressions and orchestration points, not containers for business logic. +- Favor `@State`, `@Environment`, `@Query`, `.task`, `.task(id:)`, and `onChange` before reaching for a view model. +- Inject services and shared models via `@Environment`; keep domain logic in services/models, not in the view body. +- Do not introduce a view model just to mirror local view state or wrap environment dependencies. +- If a screen is getting large, split the UI into subviews before inventing a new view model layer. + +### 3) Strongly prefer dedicated subview types over computed `some View` helpers +- Flag `body` properties that are longer than roughly one screen or contain multiple logical sections. +- Prefer extracting dedicated `View` types for non-trivial sections, especially when they have state, async work, branching, or deserve their own preview. +- Keep computed `some View` helpers rare and small. Do not build an entire screen out of `private var header: some View`-style fragments. +- Pass small, explicit inputs (data, bindings, callbacks) into extracted subviews instead of handing down the entire parent state. +- If an extracted subview becomes reusable or independently meaningful, move it to its own file. + +Prefer: + +```swift +var body: some View { + List { + HeaderSection(title: title, subtitle: subtitle) + FilterSection( + filterOptions: filterOptions, + selectedFilter: $selectedFilter + ) + ResultsSection(items: filteredItems) + FooterSection() + } +} + +private struct HeaderSection: View { + let title: String + let subtitle: String + + var body: some View { + VStack(alignment: .leading, spacing: 6) { + Text(title).font(.title2) + Text(subtitle).font(.subheadline) + } + } +} + +private struct FilterSection: View { + let filterOptions: [FilterOption] + @Binding var selectedFilter: FilterOption + + var body: some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack { + ForEach(filterOptions, id: \.self) { option in + FilterChip(option: option, isSelected: option == selectedFilter) + .onTapGesture { selectedFilter = option } + } + } + } + } +} +``` + +Avoid: + +```swift +var body: some View { + List { + header + filters + results + footer + } +} + +private var header: some View { + VStack(alignment: .leading, spacing: 6) { + Text(title).font(.title2) + Text(subtitle).font(.subheadline) + } +} +``` + +### 3b) Extract actions and side effects out of `body` +- Do not keep non-trivial button actions inline in the view body. +- Do not bury business logic inside `.task`, `.onAppear`, `.onChange`, or `.refreshable`. +- Prefer calling small private methods from the view, and move real business logic into services/models. +- The body should read like UI, not like a view controller. + +```swift +Button("Save", action: save) + .disabled(isSaving) + +.task(id: searchText) { + await reload(for: searchText) +} + +private func save() { + Task { await saveAsync() } +} + +private func reload(for searchText: String) async { + guard !searchText.isEmpty else { + results = [] + return + } + await searchService.search(searchText) +} +``` + +### 4) Keep a stable view tree (avoid top-level conditional view swapping) +- Avoid `body` or computed views that return completely different root branches via `if/else`. +- Prefer a single stable base view with conditions inside sections/modifiers (`overlay`, `opacity`, `disabled`, `toolbar`, etc.). +- Root-level branch swapping causes identity churn, broader invalidation, and extra recomputation. + +Prefer: + +```swift +var body: some View { + List { + documentsListContent + } + .toolbar { + if canEdit { + editToolbar + } + } +} +``` + +Avoid: + +```swift +var documentsListView: some View { + if canEdit { + editableDocumentsList + } else { + readOnlyDocumentsList + } +} +``` + +### 5) View model handling (only if already present or explicitly requested) +- Treat view models as a legacy or explicit-need pattern, not the default. +- Do not introduce a view model unless the request or existing code clearly calls for one. +- If a view model exists, make it non-optional when possible. +- Pass dependencies to the view via `init`, then create the view model in the view's `init`. +- Avoid `bootstrapIfNeeded` patterns and other delayed setup workarounds. + +Example (Observation-based): + +```swift +@State private var viewModel: SomeViewModel + +init(dependency: Dependency) { + _viewModel = State(initialValue: SomeViewModel(dependency: dependency)) +} +``` + +### 6) Observation usage +- For `@Observable` reference types on iOS 17+, store them as `@State` in the owning view. +- Pass observables down explicitly; avoid optional state unless the UI genuinely needs it. +- If the deployment target includes iOS 16 or earlier, use `@StateObject` at the owner and `@ObservedObject` when injecting legacy observable models. + +## Workflow + +1. Reorder the view to match the ordering rules. +2. Remove inline actions and side effects from `body`; move business logic into services/models and keep only thin orchestration in the view. +3. Shorten long bodies by extracting dedicated subview types; avoid rebuilding the screen out of many computed `some View` helpers. +4. Ensure stable view structure: avoid top-level `if`-based branch swapping; move conditions to localized sections/modifiers. +5. If a view model exists or is explicitly required, replace optional view models with a non-optional `@State` view model initialized in `init`. +6. Confirm Observation usage: `@State` for root `@Observable` models on iOS 17+, legacy wrappers only when the deployment target requires them. +7. Keep behavior intact: do not change layout or business logic unless requested. + +## Notes + +- Prefer small, explicit view types over large conditional blocks and large computed `some View` properties. +- Keep computed view builders below `body` and non-view computed vars above `init`. +- A good SwiftUI refactor should make the view read top-to-bottom as data flow plus layout, not as mixed layout and imperative logic. +- For MV-first guidance and rationale, see `references/mv-patterns.md`. + +## Large-view handling + +When a SwiftUI view file exceeds ~300 lines, split it aggressively. Extract meaningful sections into dedicated `View` types instead of hiding complexity in many computed properties. Use `private` extensions with `// MARK: -` comments for actions and helpers, but do not treat extensions as a substitute for breaking a giant screen into smaller view types. If an extracted subview is reused or independently meaningful, move it into its own file. diff --git a/skills/swiftui-view-refactor/agents/openai.yaml b/skills/swiftui-view-refactor/agents/openai.yaml new file mode 100644 index 00000000..5cc30e51 --- /dev/null +++ b/skills/swiftui-view-refactor/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "SwiftUI View Refactor" + short_description: "Refactor large SwiftUI view files" + default_prompt: "Use $swiftui-view-refactor to clean up and split this SwiftUI view without changing its behavior." diff --git a/skills/swiftui-view-refactor/references/mv-patterns.md b/skills/swiftui-view-refactor/references/mv-patterns.md new file mode 100644 index 00000000..12d4bccc --- /dev/null +++ b/skills/swiftui-view-refactor/references/mv-patterns.md @@ -0,0 +1,161 @@ +# MV Patterns Reference + +Distilled guidance for deciding whether a SwiftUI feature should stay as plain MV or introduce a view model. + +Inspired by the user's provided source, "SwiftUI in 2025: Forget MVVM" (Thomas Ricouard), but rewritten here as a practical refactoring reference. + +## Default stance + +- Default to MV: views are lightweight state expressions and orchestration points. +- Prefer `@State`, `@Environment`, `@Query`, `.task`, `.task(id:)`, and `onChange` before reaching for a view model. +- Keep business logic in services, models, or domain types, not in the view body. +- Split large screens into smaller view types before inventing a view model layer. +- Avoid manual fetching or state plumbing that duplicates SwiftUI or SwiftData mechanisms. +- Test services, models, and transformations first; views should stay simple and declarative. + +## When to avoid a view model + +Do not introduce a view model when it would mostly: +- mirror local view state, +- wrap values already available through `@Environment`, +- duplicate `@Query`, `@State`, or `Binding`-based data flow, +- exist only because the view body is too long, +- hold one-off async loading logic that can live in `.task` plus local view state. + +In these cases, simplify the view and data flow instead of adding indirection. + +## When a view model may be justified + +A view model can be reasonable when at least one of these is true: +- the user explicitly asks for one, +- the codebase already standardizes on a view model pattern for that feature, +- the screen needs a long-lived reference model with behavior that does not fit naturally in services alone, +- the feature is adapting a non-SwiftUI API that needs a dedicated bridge object, +- multiple views share the same presentation-specific state and that state is not better modeled as app-level environment data. + +Even then, keep the view model small, explicit, and non-optional when possible. + +## Preferred pattern: local state plus environment + +```swift +struct FeedView: View { + @Environment(BlueSkyClient.self) private var client + + enum ViewState { + case loading + case error(String) + case loaded([Post]) + } + + @State private var viewState: ViewState = .loading + + var body: some View { + List { + switch viewState { + case .loading: + ProgressView("Loading feed...") + case .error(let message): + ErrorStateView(message: message, retryAction: { await loadFeed() }) + case .loaded(let posts): + ForEach(posts) { post in + PostRowView(post: post) + } + } + } + .task { await loadFeed() } + } + + private func loadFeed() async { + do { + let posts = try await client.getFeed() + viewState = .loaded(posts) + } catch { + viewState = .error(error.localizedDescription) + } + } +} +``` + +Why this is preferred: +- state stays close to the UI that renders it, +- dependencies come from the environment instead of a wrapper object, +- the view coordinates UI flow while the service owns the real work. + +## Preferred pattern: use modifiers as lightweight orchestration + +```swift +.task(id: searchText) { + guard !searchText.isEmpty else { + results = [] + return + } + await searchFeed(query: searchText) +} + +.onChange(of: isInSearch, initial: false) { + guard !isInSearch else { return } + Task { await fetchSuggestedFeed() } +} +``` + +Use view lifecycle modifiers for simple, local orchestration. Do not convert these into a view model by default unless the behavior clearly outgrows the view. + +## SwiftData note + +SwiftData is a strong argument for keeping data flow inside the view when possible. + +Prefer: + +```swift +struct BookListView: View { + @Query private var books: [Book] + @Environment(\.modelContext) private var modelContext + + var body: some View { + List { + ForEach(books) { book in + BookRowView(book: book) + .swipeActions { + Button("Delete", role: .destructive) { + modelContext.delete(book) + } + } + } + } + } +} +``` + +Avoid adding a view model that manually fetches and mirrors the same state unless the feature has an explicit reason to do so. + +## Testing guidance + +Prefer to test: +- services and business rules, +- models and state transformations, +- async workflows at the service layer, +- UI behavior with previews or higher-level UI tests. + +Do not introduce a view model primarily to make a simple SwiftUI view "testable." That usually adds ceremony without improving the architecture. + +## Refactor checklist + +When refactoring toward MV: +- Remove view models that only wrap environment dependencies or local view state. +- Replace optional or delayed-initialized view models when plain view state is enough. +- Pull business logic out of the view body and into services/models. +- Keep the view as a thin coordinator of UI state, navigation, and user actions. +- Split large bodies into smaller view types before adding new layers of indirection. + +## Bottom line + +Treat view models as the exception, not the default. + +In modern SwiftUI, the default stack is: +- `@State` for local state, +- `@Environment` for shared dependencies, +- `@Query` for SwiftData-backed collections, +- lifecycle modifiers for lightweight orchestration, +- services and models for business logic. + +Reach for a view model only when the feature clearly needs one. diff --git a/skills_index.json b/skills_index.json index 95029d41..d3ff07cb 100644 --- a/skills_index.json +++ b/skills_index.json @@ -899,6 +899,16 @@ "source": "community", "date_added": "2026-02-27" }, + { + "id": "app-store-changelog", + "path": "skills/app-store-changelog", + "category": "uncategorized", + "name": "app-store-changelog", + "description": "Generate user-facing App Store release notes from git history since the last tag.", + "risk": "safe", + "source": "Dimillian/Skills (MIT)", + "date_added": "2026-03-25" + }, { "id": "app-store-optimization", "path": "skills/app-store-optimization", @@ -6039,6 +6049,16 @@ "source": "community", "date_added": "2026-02-27" }, + { + "id": "github", + "path": "skills/github", + "category": "uncategorized", + "name": "github", + "description": "Use the `gh` CLI for issues, pull requests, Actions runs, and GitHub API queries.", + "risk": "safe", + "source": "Dimillian/Skills (MIT)", + "date_added": "2026-03-25" + }, { "id": "github-actions-templates", "path": "skills/github-actions-templates", @@ -6899,6 +6919,16 @@ "source": "https://github.com/ai-evos/agent-skills", "date_added": "2026-02-27" }, + { + "id": "ios-debugger-agent", + "path": "skills/ios-debugger-agent", + "category": "mobile", + "name": "ios-debugger-agent", + "description": "Debug the current iOS project on a booted simulator with XcodeBuildMCP.", + "risk": "safe", + "source": "Dimillian/Skills (MIT)", + "date_added": "2026-03-25" + }, { "id": "ios-developer", "path": "skills/ios-developer", @@ -7609,6 +7639,26 @@ "source": "community", "date_added": "2026-02-27" }, + { + "id": "macos-menubar-tuist-app", + "path": "skills/macos-menubar-tuist-app", + "category": "uncategorized", + "name": "macos-menubar-tuist-app", + "description": "Build, refactor, or review SwiftUI macOS menubar apps that use Tuist.", + "risk": "safe", + "source": "Dimillian/Skills (MIT)", + "date_added": "2026-03-25" + }, + { + "id": "macos-spm-app-packaging", + "path": "skills/macos-spm-app-packaging", + "category": "uncategorized", + "name": "macos-spm-app-packaging", + "description": "Scaffold, build, sign, and package SwiftPM macOS apps without Xcode projects.", + "risk": "safe", + "source": "Dimillian/Skills (MIT)", + "date_added": "2026-03-25" + }, { "id": "magic-animator", "path": "skills/magic-animator", @@ -8909,6 +8959,16 @@ "source": "community", "date_added": null }, + { + "id": "orchestrate-batch-refactor", + "path": "skills/orchestrate-batch-refactor", + "category": "uncategorized", + "name": "orchestrate-batch-refactor", + "description": "Plan and execute large refactors with dependency-aware work packets and parallel analysis.", + "risk": "safe", + "source": "Dimillian/Skills (MIT)", + "date_added": "2026-03-25" + }, { "id": "os-scripting", "path": "skills/os-scripting", @@ -9519,6 +9579,16 @@ "source": "community", "date_added": null }, + { + "id": "project-skill-audit", + "path": "skills/project-skill-audit", + "category": "uncategorized", + "name": "project-skill-audit", + "description": "Audit a project and recommend the highest-value skills to add or update.", + "risk": "safe", + "source": "Dimillian/Skills (MIT)", + "date_added": "2026-03-25" + }, { "id": "projection-patterns", "path": "skills/projection-patterns", @@ -9779,6 +9849,16 @@ "source": "community", "date_added": "2026-02-27" }, + { + "id": "react-component-performance", + "path": "skills/react-component-performance", + "category": "web-development", + "name": "react-component-performance", + "description": "Diagnose slow React components and suggest targeted performance fixes.", + "risk": "safe", + "source": "Dimillian/Skills (MIT)", + "date_added": "2026-03-25" + }, { "id": "react-flow-architect", "path": "skills/react-flow-architect", @@ -10919,6 +10999,16 @@ "source": "community", "date_added": "2026-02-27" }, + { + "id": "simplify-code", + "path": "skills/simplify-code", + "category": "uncategorized", + "name": "simplify-code", + "description": "Review a diff for clarity and safe simplifications, then optionally apply low-risk fixes.", + "risk": "safe", + "source": "Dimillian/Skills (MIT)", + "date_added": "2026-03-25" + }, { "id": "site-architecture", "path": "skills/site-architecture", @@ -11119,6 +11209,16 @@ "source": "community", "date_added": "2026-02-27" }, + { + "id": "snowflake-development", + "path": "skills/snowflake-development", + "category": "data-engineering", + "name": "snowflake-development", + "description": "Comprehensive Snowflake development assistant covering SQL best practices, data pipeline design (Dynamic Tables, Streams, Tasks, Snowpipe), Cortex AI functions, Cortex Agents, Snowpark Python, dbt integration, performance tuning, and security hardening.", + "risk": "safe", + "source": "community", + "date_added": "2026-03-24" + }, { "id": "social-content", "path": "skills/social-content", @@ -11479,6 +11579,16 @@ "source": "community", "date_added": "2026-03-18" }, + { + "id": "swift-concurrency-expert", + "path": "skills/swift-concurrency-expert", + "category": "uncategorized", + "name": "swift-concurrency-expert", + "description": "Review and fix Swift concurrency issues such as actor isolation and Sendable violations.", + "risk": "safe", + "source": "Dimillian/Skills (MIT)", + "date_added": "2026-03-25" + }, { "id": "swiftui-expert-skill", "path": "skills/swiftui-expert-skill", @@ -11489,6 +11599,46 @@ "source": "community", "date_added": null }, + { + "id": "swiftui-liquid-glass", + "path": "skills/swiftui-liquid-glass", + "category": "mobile", + "name": "swiftui-liquid-glass", + "description": "Implement or review SwiftUI Liquid Glass APIs with correct fallbacks and modifier order.", + "risk": "safe", + "source": "Dimillian/Skills (MIT)", + "date_added": "2026-03-25" + }, + { + "id": "swiftui-performance-audit", + "path": "skills/swiftui-performance-audit", + "category": "mobile", + "name": "swiftui-performance-audit", + "description": "Audit SwiftUI performance issues from code review and profiling evidence.", + "risk": "safe", + "source": "Dimillian/Skills (MIT)", + "date_added": "2026-03-25" + }, + { + "id": "swiftui-ui-patterns", + "path": "skills/swiftui-ui-patterns", + "category": "mobile", + "name": "swiftui-ui-patterns", + "description": "Apply proven SwiftUI UI patterns for navigation, sheets, async state, and reusable screens.", + "risk": "safe", + "source": "Dimillian/Skills (MIT)", + "date_added": "2026-03-25" + }, + { + "id": "swiftui-view-refactor", + "path": "skills/swiftui-view-refactor", + "category": "mobile", + "name": "swiftui-view-refactor", + "description": "Refactor SwiftUI views into smaller components with stable, explicit data flow.", + "risk": "safe", + "source": "Dimillian/Skills (MIT)", + "date_added": "2026-03-25" + }, { "id": "sympy", "path": "skills/sympy", diff --git a/walkthrough.md b/walkthrough.md index 180db275..31dcdf8e 100644 --- a/walkthrough.md +++ b/walkthrough.md @@ -1,3 +1,43 @@ +# Maintenance Walkthrough - 2026-03-25 + +- Imported 14 skills from [Dimillian/Skills](https://github.com/Dimillian/Skills) into `skills/`: + - `app-store-changelog` + - `github` + - `ios-debugger-agent` + - `macos-menubar-tuist-app` + - `macos-spm-app-packaging` + - `orchestrate-batch-refactor` + - `project-skill-audit` + - `react-component-performance` + - `simplify-code` + - `swift-concurrency-expert` + - `swiftui-liquid-glass` + - `swiftui-performance-audit` + - `swiftui-ui-patterns` + - `swiftui-view-refactor` +- Normalized the imported skill metadata to match repository validation requirements: + - shortened oversized frontmatter descriptions + - added `risk`, `source`, and `date_added` + - added `## When to Use` sections so the imported batch does not increase the warning budget +- Added source attribution for `Dimillian/Skills` in: + - `README.md` under `Credits & Sources` + - `docs/sources/sources.md` +- Merged PR `#395` via GitHub squash merge after maintainer refresh of forked workflow approvals and PR body normalization; this added the new `snowflake-development` skill. +- Merged PR `#394` via GitHub squash merge after converting the contributor branch back to source-only, normalizing the PR checklist body, and shortening an oversized `wordpress-penetration-testing` description so CI passed. +- Patched `skills/snowflake-development/SKILL.md` on `main` with a `## When to Use` section so the repository stayed within the frozen validation warning budget after the PR merge batch. +- Ran the required direct-`main` maintainer sync flow after touching `skills/`: + - `npm run chain` + - `npm run check:warning-budget` + - `npm run catalog` +- Synced maintainer-owned generated artifacts and metadata to the new `1,325+` skill count: + - `README.md` + - `package.json` + - `skills_index.json` + - `CATALOG.md` + - `data/catalog.json` + - `data/bundles.json` + - curated user/maintainer docs updated by `sync_repo_metadata.py` + # Maintenance Walkthrough - 2026-03-21 - Imported and normalized a new batch of external skills into `skills/`, covering Anthropic Claude API/internal comms entries, marketing workflows, SEO orchestration/sub-skills, and Obsidian-focused file-format/CLI skills.