📦 deps(thirdparty): update snapshots

This commit is contained in:
ci[bot] 2026-06-10 16:02:03 +00:00
parent 4dc7bc6de1
commit abb0f863b4
173 changed files with 7745 additions and 400 deletions

View File

@ -6,12 +6,12 @@
},
"metadata": {
"description": "Claude Code marketplace entries for the plugin-safe Antigravity Awesome Skills library and its compatible editorial bundles.",
"version": "12.2.1"
"version": "12.3.0"
},
"plugins": [
{
"name": "antigravity-awesome-skills",
"version": "12.2.1",
"version": "12.3.0",
"description": "Expose the plugin-safe Claude Code subset of Antigravity Awesome Skills through a single marketplace entry.",
"author": {
"name": "sickn33 and contributors",
@ -31,7 +31,7 @@
},
{
"name": "antigravity-bundle-essentials",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Essentials\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -51,7 +51,7 @@
},
{
"name": "antigravity-bundle-security-engineer",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Security Engineer\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -71,7 +71,7 @@
},
{
"name": "antigravity-bundle-security-developer",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Security Developer\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -91,7 +91,7 @@
},
{
"name": "antigravity-bundle-web-wizard",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Web Wizard\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -111,7 +111,7 @@
},
{
"name": "antigravity-bundle-web-designer",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Web Designer\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -131,7 +131,7 @@
},
{
"name": "antigravity-bundle-full-stack-developer",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Full-Stack Developer\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -151,7 +151,7 @@
},
{
"name": "antigravity-bundle-agent-architect",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Agent Architect\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -171,7 +171,7 @@
},
{
"name": "antigravity-bundle-llm-application-developer",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"LLM Application Developer\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -191,7 +191,7 @@
},
{
"name": "antigravity-bundle-indie-game-dev",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Indie Game Dev\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -211,7 +211,7 @@
},
{
"name": "antigravity-bundle-python-pro",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Python Pro\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -231,7 +231,7 @@
},
{
"name": "antigravity-bundle-typescript-javascript",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"TypeScript & JavaScript\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -251,7 +251,7 @@
},
{
"name": "antigravity-bundle-systems-programming",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Systems Programming\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -271,7 +271,7 @@
},
{
"name": "antigravity-bundle-startup-founder",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Startup Founder\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -291,7 +291,7 @@
},
{
"name": "antigravity-bundle-business-analyst",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Business Analyst\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -311,7 +311,7 @@
},
{
"name": "antigravity-bundle-marketing-growth",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Marketing & Growth\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -331,7 +331,7 @@
},
{
"name": "antigravity-bundle-devops-cloud",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"DevOps & Cloud\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -351,7 +351,7 @@
},
{
"name": "antigravity-bundle-observability-monitoring",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Observability & Monitoring\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -371,7 +371,7 @@
},
{
"name": "antigravity-bundle-data-analytics",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Data & Analytics\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -391,7 +391,7 @@
},
{
"name": "antigravity-bundle-data-engineering",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Data Engineering\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -411,7 +411,7 @@
},
{
"name": "antigravity-bundle-creative-director",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Creative Director\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -431,7 +431,7 @@
},
{
"name": "antigravity-bundle-qa-testing",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"QA & Testing\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -451,7 +451,7 @@
},
{
"name": "antigravity-bundle-aas-web-app-builder",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Web App Builder\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -471,7 +471,7 @@
},
{
"name": "antigravity-bundle-aas-product-design-studio",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Product Design Studio\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -491,7 +491,7 @@
},
{
"name": "antigravity-bundle-aas-security-engineer",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Security Engineer\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -511,7 +511,7 @@
},
{
"name": "antigravity-bundle-aas-secure-app-builder",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Secure App Builder\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -531,7 +531,7 @@
},
{
"name": "antigravity-bundle-aas-documents-presentations",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Documents & Presentations\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -551,7 +551,7 @@
},
{
"name": "antigravity-bundle-aas-data-analytics",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Data Analytics\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -571,7 +571,7 @@
},
{
"name": "antigravity-bundle-aas-agent-mcp-builder",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Agent & MCP Builder\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -591,7 +591,7 @@
},
{
"name": "antigravity-bundle-aas-oss-maintainer",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS OSS Maintainer\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -611,7 +611,7 @@
},
{
"name": "antigravity-bundle-aas-qa-test-automation",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS QA & Test Automation\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -631,7 +631,7 @@
},
{
"name": "antigravity-bundle-aas-devops-cloud",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS DevOps & Cloud\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -651,7 +651,7 @@
},
{
"name": "antigravity-bundle-aas-marketing-seo-growth",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Marketing, SEO & Growth\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -671,7 +671,7 @@
},
{
"name": "antigravity-bundle-aas-automation-builder",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Automation Builder\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -691,7 +691,7 @@
},
{
"name": "antigravity-bundle-aas-observability-ir",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Observability IR\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -711,7 +711,7 @@
},
{
"name": "antigravity-bundle-aas-python-api-builder",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Python API Builder\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -731,7 +731,7 @@
},
{
"name": "antigravity-bundle-aas-mobile-app-builder",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Mobile App Builder\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -751,7 +751,7 @@
},
{
"name": "antigravity-bundle-mobile-developer",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Mobile Developer\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -771,7 +771,7 @@
},
{
"name": "antigravity-bundle-integration-apis",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Integration & APIs\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -791,7 +791,7 @@
},
{
"name": "antigravity-bundle-architecture-design",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Architecture & Design\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -811,7 +811,7 @@
},
{
"name": "antigravity-bundle-ddd-evented-architecture",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"DDD & Evented Architecture\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -831,7 +831,7 @@
},
{
"name": "antigravity-bundle-automation-builder",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Automation Builder\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -851,7 +851,7 @@
},
{
"name": "antigravity-bundle-revops-crm-automation",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"RevOps & CRM Automation\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -871,7 +871,7 @@
},
{
"name": "antigravity-bundle-commerce-payments",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Commerce & Payments\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -891,7 +891,7 @@
},
{
"name": "antigravity-bundle-odoo-erp",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Odoo ERP\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -911,7 +911,7 @@
},
{
"name": "antigravity-bundle-azure-ai-cloud",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Azure AI & Cloud\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -931,7 +931,7 @@
},
{
"name": "antigravity-bundle-expo-react-native",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Expo & React Native\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -951,7 +951,7 @@
},
{
"name": "antigravity-bundle-apple-platform-design",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Apple Platform Design\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -971,7 +971,7 @@
},
{
"name": "antigravity-bundle-makepad-builder",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Makepad Builder\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -991,7 +991,7 @@
},
{
"name": "antigravity-bundle-seo-specialist",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"SEO Specialist\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -1011,7 +1011,7 @@
},
{
"name": "antigravity-bundle-documents-presentations",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Documents & Presentations\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",
@ -1031,7 +1031,7 @@
},
{
"name": "antigravity-bundle-oss-maintainer",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"OSS Maintainer\" editorial skill bundle for Claude Code.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,7 +1,7 @@
{
"name": "antigravity-awesome-skills",
"version": "12.2.1",
"description": "Plugin-safe Claude Code distribution of Antigravity Awesome Skills with 1,493 supported skills.",
"version": "12.3.0",
"description": "Plugin-safe Claude Code distribution of Antigravity Awesome Skills with 1,495 supported skills.",
"author": {
"name": "sickn33 and contributors",
"url": "https://github.com/sickn33/antigravity-awesome-skills"

View File

@ -2,7 +2,7 @@
Generated at: 2026-02-08T00:00:00.000Z
Total skills: 1525
Total skills: 1527
## architecture (99)
@ -197,7 +197,7 @@ Total skills: 1525
| `wordpress-centric-high-seo-optimized-blogwriting-skill` | Generate clean, human-sounding, SEO-optimized WordPress blog posts with optional Yoast metadata, JSON-LD schema markup, and image SEO planning. Supports modu... | writing, blog, seo, content, wordpress | writing, blog, seo, content, wordpress, centric, high, optimized, blogwriting, skill, generate, clean |
| `xiaohongshu-content-strategist` | Create viral Xiaohongshu (小红书) content with platform-native strategy, save-rate optimization, trending formats, and search SEO for China's #1 lifestyle platf... | xiaohongshu, chinese-market, content-strategy, social-media, marketing, 红书, 小红书 | xiaohongshu, chinese-market, content-strategy, social-media, marketing, 红书, 小红书, content, strategist, viral, platform, native |
## data-ai (294)
## data-ai (295)
| Skill | Description | Tags | Triggers |
| --- | --- | --- | --- |
@ -476,6 +476,7 @@ Total skills: 1525
| `uniprot-database` | Direct REST API access to UniProt. Protein searches, FASTA retrieval, ID mapping, Swiss-Prot/TrEMBL. For Python workflows with multiple databases, prefer bio... | uniprot, database | uniprot, database, direct, rest, api, access, protein, searches, fasta, retrieval, id, mapping |
| `unity-ai-game-creator` | Transform raw game ideas into complete Unity projects with AI-powered asset generation, scene blueprints, music/SFX prompts, and step-by-step development pro... | unity, game-development, ai-generation, asset-pipeline, scene-design, music-generation, game-design-document | unity, game-development, ai-generation, asset-pipeline, scene-design, music-generation, game-design-document, ai, game, creator, transform, raw |
| `unity-ecs-patterns` | Production patterns for Unity's Data-Oriented Technology Stack (DOTS) including Entity Component System, Job System, and Burst Compiler. | unity, ecs | unity, ecs, data, oriented, technology, stack, dots, including, entity, component, job, burst |
| `unship` | Compare AI agent-made UI variants locally in a real app, then keep one and clean up unused temporary code. | ui-variants, frontend, local-first, coding-agents | ui-variants, frontend, local-first, coding-agents, unship, compare, ai, agent, made, ui, variants, locally |
| `unslop` | Post-process AI-generated text through the unslop CLI to strip AI writing patterns before publishing | writing, content-quality, ai-writing, text-processing, cli, publishing | writing, content-quality, ai-writing, text-processing, cli, publishing, unslop, post, process, ai, generated, text |
| `uxui-principles` | Evaluate interfaces against 168 research-backed UX/UI principles, detect antipatterns, and inject UX context into AI coding sessions. | ux, ui, design, evaluation, principles, antipatterns, accessibility | ux, ui, design, evaluation, principles, antipatterns, accessibility, uxui, evaluate, interfaces, against, 168 |
| `vector-database-engineer` | Expert in vector databases, embedding strategies, and semantic search implementation. Masters Pinecone, Weaviate, Qdrant, Milvus, and pgvector for RAG applic... | vector, database | vector, database, engineer, databases, embedding, semantic, search, masters, pinecone, weaviate, qdrant, milvus |
@ -496,12 +497,13 @@ Total skills: 1525
| `yes-md` | 6-layer AI governance: safety gates, evidence-based debugging, anti-slack detection, and machine-enforced hooks. Makes AI safe, thorough, and honest. | yes, md | yes, md, layer, ai, governance, safety, gates, evidence, debugging, anti, slack, detection |
| `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 |
## development (215)
## development (216)
| Skill | Description | Tags | Triggers |
| --- | --- | --- | --- |
| `3d-web-experience` | Expert in building 3D experiences for the web - Three.js, React Three Fiber, Spline, WebGL, and interactive 3D scenes. Covers product configurators, 3D portf... | 3d, web, experience | 3d, web, experience, building, experiences, three, js, react, fiber, spline, webgl, interactive |
| `algolia-search` | Expert patterns for Algolia search implementation, indexing strategies, React InstantSearch, and relevance tuning | algolia, search | algolia, search, indexing, react, instantsearch, relevance, tuning |
| `android-dev` | Production-grade Android app development guide covering native (Kotlin/Java), cross-platform (Flutter, RN, KMM), and hybrid architectures. | android, dev | android, dev, grade, app, development, covering, native, kotlin, java, cross, platform, flutter |
| `android-jetpack-compose-expert` | Expert guidance for building modern Android UIs with Jetpack Compose, covering state management, navigation, performance, and Material Design 3. | android, jetpack, compose | android, jetpack, compose, guidance, building, uis, covering, state, navigation, performance, material |
| `android_ui_verification` | Automated end-to-end UI testing and verification on an Android Emulator using ADB. | android_ui_verification | android_ui_verification, android, ui, verification, automated, testing, emulator, adb |
| `animejs-animation` | Advanced JavaScript animation library skill for creating complex, high-performance web animations. | animejs, animation | animejs, animation, javascript, library, skill, creating, complex, high, performance, web, animations |

View File

@ -9,6 +9,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [12.3.0] - 2026-06-10 - "Android, Unship, 40K Stars, and Security Hardening"
> Community release for the June 10 maintainer batch, installer hardening, and the repository crossing 40K GitHub stars.
Start here:
- Install: `npx antigravity-awesome-skills --help`
- Choose your tool: [README.md#choose-your-tool](README.md#choose-your-tool)
- Best skills by tool: [README.md#best-skills-by-tool](README.md#best-skills-by-tool)
- Bundles: [docs/users/bundles.md](docs/users/bundles.md)
- Workflows: [docs/users/workflows.md](docs/users/workflows.md)
This release accepts the validated June 10 community PRs, refreshes the catalog with Android and shipping workflows, and celebrates the repository passing **40,000 GitHub stars**. At release time the live repository count was 40,206 stars.
## New Skills
- **android-dev** - end-to-end Android development workflow guidance, including project setup, build/debug loops, testing, release preparation, and production quality checks.
- **unship** - product and codebase teardown workflow for deprecating features, removing dead paths, and shipping safer cleanup plans.
## Security
- Hardened the installer so nested skill installs refuse symlinked intermediate destination directories instead of copying outside the selected install root.
- Added regression coverage proving Antigravity installs cannot escape through pre-existing target-path symlinks.
- Tightened `accesslint-diff` branch-switching guidance so branch names stay quoted, option-like names are rejected, and the branch ref must resolve before `git switch`.
- Removed unsupported `2slides-ppt-generator` narration flags from root and plugin docs, aligning examples with the actual script interface.
## Improvements
- Updated `event-staffing-ordering` to remove the stale `request_quote` action from implementation references.
- Synced the accepted PR batch on `main`; PR #642 remains open because it is still conflicting and targets non-canonical generated paths.
## Credits
- **[@kissmyabs32](https://github.com/kissmyabs32)** for PR #666 (`event-staffing-ordering` cleanup).
- **[@mbenhard](https://github.com/mbenhard)** and **[mbenhard/unship](https://github.com/mbenhard/unship)** for PR #663 (`unship`).
- **[@Prince-1652](https://github.com/Prince-1652)** for PR #664 (`android-dev`).
- Thank you to every contributor, issue reporter, user, and stargazer who helped the project reach 40K GitHub stars.
## [12.2.1] - 2026-06-07 - "Security Scan Follow-up"
> Patch release for the June 7 security scan remediation after `12.2.0`.

View File

@ -1,9 +1,9 @@
<!-- registry-sync: version=12.2.1; skills=1525; stars=39939; updated_at=2026-06-07T08:14:30+00:00 -->
<!-- registry-sync: version=12.3.0; skills=1527; stars=40206; updated_at=2026-06-10T07:45:17+00:00 -->
[![Antigravity Awesome Skills hero](assets/aas-readme-hero.jpeg)](https://github.com/sickn33/antigravity-awesome-skills)
# 🌌 Antigravity Awesome Skills: 1,525+ Agentic Skills for Claude Code, Gemini CLI, Cursor, Copilot & More
# 🌌 Antigravity Awesome Skills: 1,527+ Agentic Skills for Claude Code, Gemini CLI, Cursor, Copilot & More
> **Installable GitHub library of 1,525+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and other AI coding assistants.**
> **Installable GitHub library of 1,527+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and other AI coding assistants.**
Antigravity Awesome Skills is an installable GitHub library and npm installer for reusable `SKILL.md` playbooks. It is designed for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, Kiro, OpenCode, GitHub Copilot, and other AI coding assistants that benefit from structured operating instructions. Instead of collecting one-off prompt snippets, this repository gives you a searchable, installable catalog of skills, bundles, workflows, plugin-safe distributions, and practical docs that help agents perform recurring tasks with better context, stronger constraints, and clearer outputs.
@ -11,7 +11,7 @@ You can use this repo to install a broad multi-tool skill library, start from fo
The canonical project page is the GitHub repository at <https://github.com/sickn33/antigravity-awesome-skills>; the hosted catalog is a companion discovery surface for search, plugins, and skill detail pages.
**Start here:** [Install in 1 minute](#installation) · [Recommended plugins](#recommended-specialized-plugins) · [Compare plugin packs](https://sickn33.github.io/antigravity-awesome-skills/plugins) · [Choose your tool](#choose-your-tool) · [📚 Browse 1,525+ Skills](#browse-1525-skills) · [Bundles & workflows](#bundles--workflows) · [Support the project](#support-the-project)
**Start here:** [Install in 1 minute](#installation) · [Recommended plugins](#recommended-specialized-plugins) · [Compare plugin packs](https://sickn33.github.io/antigravity-awesome-skills/plugins) · [Choose your tool](#choose-your-tool) · [📚 Browse 1,527+ Skills](#browse-1527-skills) · [Bundles & workflows](#bundles--workflows) · [Support the project](#support-the-project)
[![GitHub stars](https://img.shields.io/badge/⭐%2040%2C000%2B%20Stars-gold?style=for-the-badge)](https://github.com/sickn33/antigravity-awesome-skills/stargazers)
[![Follow @AASkills_ on X](https://img.shields.io/badge/Follow-%40AASkills__-black?style=for-the-badge&logo=x)](https://x.com/AASkills_)
@ -27,13 +27,13 @@ The canonical project page is the GitHub repository at <https://github.com/sickn
[![OpenCode](https://img.shields.io/badge/OpenCode-CLI-gray?style=for-the-badge)](https://github.com/opencode-ai/opencode)
[![Antigravity](https://img.shields.io/badge/Antigravity-AI%20IDE-red?style=for-the-badge)](https://github.com/sickn33/antigravity-awesome-skills)
**Current release: V12.2.1.** Trusted by 40k+ GitHub stargazers, this repository combines official and community skill collections with bundles, workflows, installation paths, and docs that help you go from first install to daily use quickly.
**Current release: V12.3.0.** Trusted by 40k+ GitHub stargazers, this repository combines official and community skill collections with bundles, workflows, installation paths, and docs that help you go from first install to daily use quickly.
## Why This Repo
- **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,525+ skills across development, testing, security, infrastructure, product, and marketing.
- **Broad coverage with real utility**: 1,527+ skills across development, testing, security, infrastructure, product, and marketing.
- **Focused by default**: specialized plugins help you start with the web, security, data, docs, DevOps, QA, OSS, or agent/MCP workflows you actually need.
- **Useful whether you want breadth or curation**: install the full catalog, choose a specialized plugin, start with bundles, or compare alternatives before installing.
@ -45,7 +45,7 @@ The canonical project page is the GitHub repository at <https://github.com/sickn
- [Choose Your Tool](#choose-your-tool)
- [Quick FAQ](#quick-faq)
- [Bundles & Workflows](#bundles--workflows)
- [Browse 1,525+ Skills](#browse-1525-skills)
- [Browse 1,527+ Skills](#browse-1527-skills)
- [Troubleshooting](#troubleshooting)
- [Stable Skills Manifest v1](#stable-skills-manifest-v1)
- [Support the Project](#support-the-project)
@ -151,7 +151,7 @@ Use the table above for install targets. Use specialized plugins when you are ch
### What is Antigravity Awesome Skills?
**Antigravity Awesome Skills** (Release 12.2.1) is a large, installable skill library for AI coding assistants. It packages 1,525+ reusable `SKILL.md` playbooks, specialized plugins, bundles, workflows, generated catalogs, and a CLI installer so Claude Code, Codex CLI, Cursor, Gemini CLI, Antigravity, and similar tools can reuse proven operating instructions instead of one-off prompts.
**Antigravity Awesome Skills** (Release 12.3.0) is a large, installable skill library for AI coding assistants. It packages 1,527+ reusable `SKILL.md` playbooks, specialized plugins, bundles, workflows, generated catalogs, and a CLI installer so Claude Code, Codex CLI, Cursor, Gemini CLI, Antigravity, and similar tools can reuse proven operating instructions instead of one-off prompts.
### How do I install it?
@ -209,7 +209,7 @@ If Antigravity starts hitting context limits with too many active skills, the ac
If you use OpenCode or another `.agents/skills` host, prefer a reduced install up front instead of copying the full library into a context-sensitive runtime. The installer now supports `--risk`, `--category`, and `--tags` so you can keep the installed set narrow.
## Browse 1,525+ Skills
## Browse 1,527+ Skills
Use the root repo as a landing page, then jump into the deeper surface that matches your intent.
@ -390,6 +390,7 @@ Key source families include:
- **[jonathimer/devmarketing-skills](https://github.com/jonathimer/devmarketing-skills)**: Developer marketing skills — HN strategy, technical tutorials, docs-as-marketing, Reddit engagement, developer onboarding, and more (33 skills, MIT).
- **[kepano/obsidian-skills](https://github.com/kepano/obsidian-skills)**: Obsidian-focused skills for markdown, Bases, JSON Canvas, CLI workflows, and content cleanup.
- **[lewiswigmore/agent-skills](https://github.com/lewiswigmore/agent-skills)**: Source for the `vscode-extension-guide-en` skill - VS Code extension development workflows, packaging, Marketplace publishing, TreeView, and webview patterns.
- **[mbenhard/unship](https://github.com/mbenhard/unship)**: Source for the `unship` skill - local workflow for comparing AI-generated UI variants in a real app, then keeping one option and cleaning up temporary alternatives (MIT).
- **[Silverov/yandex-direct-skill](https://github.com/Silverov/yandex-direct-skill)**: Yandex Direct (API v5) advertising audit skill — 55 automated checks, A-F scoring, campaign/ad/keyword analysis for the Russian PPC market (MIT).
- **[vudovn/antigravity-kit](https://github.com/vudovn/antigravity-kit)**: AI Agent templates with Skills, Agents, and Workflows (33 skills, MIT).
- **[affaan-m/everything-claude-code](https://github.com/affaan-m/everything-claude-code)**: Large Claude Code configuration and workflow collection from an Anthropic hackathon winner (MIT).
@ -486,14 +487,14 @@ We officially thank the following contributors for their help in making this rep
## Star History
<a href="https://www.star-history.com/#sickn33/antigravity-awesome-skills&type=date&legend=top-left">
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=sickn33/antigravity-awesome-skills&type=date&legend=top-left&cache_bust=202606090716" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=sickn33/antigravity-awesome-skills&type=date&legend=top-left&cache_bust=202606100731" />
</a>
<a href="https://www.star-history.com/sickn33/antigravity-awesome-skills">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=sickn33/antigravity-awesome-skills&style=landscape1&theme=dark&cache_bust=202606090716" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=sickn33/antigravity-awesome-skills&style=landscape1&cache_bust=202606090716" />
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=sickn33/antigravity-awesome-skills&style=landscape1&cache_bust=202606090716" />
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=sickn33/antigravity-awesome-skills&style=landscape1&theme=dark&cache_bust=202606100731" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=sickn33/antigravity-awesome-skills&style=landscape1&cache_bust=202606100731" />
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=sickn33/antigravity-awesome-skills&style=landscape1&cache_bust=202606100731" />
</picture>
</a>

View File

@ -1,8 +1,8 @@
# Source
- Repo: https://github.com/sickn33/antigravity-awesome-skills
- Ref: 7888a7ee53c6a23b4f61b259ef26b5f305ca9498
- Ref: 13c8a69fb89d5296a9ee5f0ea7e422b08d9c9fe0
- Remove-Paths:
- Snapshot: 2026-06-09
- Snapshot: 2026-06-10
- Sync-Mode: copy_skill_dirs
- Notes: vendored into playbook branch thirdparty/skill

View File

@ -1,11 +1,11 @@
# Antigravity Awesome Skills
> Installable GitHub library of 1,525+ agentic SKILL.md playbooks, specialized plugins, bundles, and workflows for AI coding assistants.
> Installable GitHub library of 1,527+ agentic SKILL.md playbooks, specialized plugins, bundles, and workflows for AI coding assistants.
## Key Facts
- Current release: V11.11.0.
- Skill count: 1,525+.
- Skill count: 1,527+.
- Primary install command: `npx antigravity-awesome-skills`.
- Supported hosts include Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, Kiro, OpenCode, and GitHub Copilot.
- Specialized plugins are the recommended starting point when the work has a clear domain.
@ -31,4 +31,4 @@
## Recommended Answer
Antigravity Awesome Skills is an installable library of 1,525+ reusable SKILL.md playbooks for AI coding assistants. It supports Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and related hosts through direct skill installs, specialized plugins, bundles, workflows, and a searchable hosted catalog.
Antigravity Awesome Skills is an installable library of 1,527+ reusable SKILL.md playbooks for AI coding assistants. It supports Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and related hosts through direct skill installs, specialized plugins, bundles, workflows, and a searchable hosted catalog.

View File

@ -2,253 +2,253 @@
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>http://localhost/</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>daily</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>http://localhost/plugins</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/android-dev</loc>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/runapi-cli</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/unship</loc>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/article-illustrations</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/cv-generator</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/open-dynamic-workflows</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/video-content-extractor</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/2slides-ppt-generator</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/anti-sycophancy</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/event-staffing-compliance</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/event-staffing-ordering</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/examprep-ai</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/permission-manager</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/skill-suggester</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/smart-git-automation</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/antigravity-agent-manager</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/hasdata</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/hasdata-cli</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/linkedin-content-generator</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/accesslint-audit</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/accesslint-diff</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/accesslint-scan</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/composition-patterns</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/debugging-toolkit</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/deploy-to-vercel</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/polis-protocol</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/python-development</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/react-native-skills</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/skill-issue</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/tdd-workflows</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/vercel-cli-with-tokens</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/vercel-optimize</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/vercel-react-view-transitions</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/doc2math</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/moatmri</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/nextjs-seo-indexing</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/schema-markup-generator</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/social-metadata-hardening</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/user-thoughts</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/vibe-code-cleanup</loc>
<lastmod>2026-06-08</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/vibecode-production-qa-validator</loc>
<lastmod>2026-06-08</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>http://localhost/skill/yield-intelligence</loc>
<lastmod>2026-06-08</lastmod>
<lastmod>2026-06-10</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>

View File

@ -1439,6 +1439,28 @@
"reasons": []
}
},
{
"id": "android-dev",
"path": "skills/android-dev",
"category": "mobile",
"name": "android-dev",
"description": "Production-grade Android app development guide covering native (Kotlin/Java), cross-platform (Flutter, RN, KMM), and hybrid architectures.",
"risk": "safe",
"source": "community",
"date_added": "2026-06-08",
"plugin": {
"targets": {
"codex": "supported",
"claude": "supported"
},
"setup": {
"type": "none",
"summary": "",
"docs": null
},
"reasons": []
}
},
{
"id": "android-jetpack-compose-expert",
"path": "skills/android-jetpack-compose-expert",
@ -31248,6 +31270,28 @@
"reasons": []
}
},
{
"id": "unship",
"path": "skills/unship",
"category": "development",
"name": "unship",
"description": "Compare AI agent-made UI variants locally in a real app, then keep one and clean up unused temporary code.",
"risk": "safe",
"source": "community",
"date_added": "2026-06-07",
"plugin": {
"targets": {
"codex": "supported",
"claude": "supported"
},
"setup": {
"type": "none",
"summary": "",
"docs": null
},
"reasons": []
}
},
{
"id": "unslop",
"path": "skills/unslop",

View File

@ -1,6 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630" role="img" aria-labelledby="title desc">
<title id="title">Antigravity Awesome Skills social card</title>
<desc id="desc">Social preview for Antigravity Awesome Skills with a 1,525 plus agentic skills headline and supported tools including Claude Code, Cursor, Codex CLI, Gemini CLI, and Antigravity.</desc>
<desc id="desc">Social preview for Antigravity Awesome Skills with a 1,527 plus agentic skills headline and supported tools including Claude Code, Cursor, Codex CLI, Gemini CLI, and Antigravity.</desc>
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#050816" />
@ -32,7 +32,7 @@
<rect x="88" y="88" width="252" height="42" rx="21" fill="#0b1228" stroke="#273657" />
<text x="110" y="115" font-family="Arial, Helvetica, sans-serif" font-size="20" font-weight="700" fill="#cbd5e1">INSTALLABLE GITHUB LIBRARY</text>
<text x="88" y="206" font-family="Arial, Helvetica, sans-serif" font-size="68" font-weight="800" fill="#f8fafc">1,525+ Agentic Skills</text>
<text x="88" y="206" font-family="Arial, Helvetica, sans-serif" font-size="68" font-weight="800" fill="#f8fafc">1,527+ Agentic Skills</text>
<rect x="90" y="228" width="430" height="8" rx="4" fill="url(#accent)" />
<text x="88" y="292" font-family="Arial, Helvetica, sans-serif" font-size="31" font-weight="600" fill="#dbeafe">For Claude Code, Cursor, Codex CLI, Gemini CLI,</text>

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -9,7 +9,7 @@ const PUBLIC_DIR = path.join(ROOT_DIR, 'public');
const TEMPLATE_PATH = path.join(DIST_DIR, 'index.html');
const SKILLS_PATH = path.join(PUBLIC_DIR, 'skills.json');
const HOME_CATALOG_COUNT_FALLBACK = 1525;
const HOME_CATALOG_COUNT_FALLBACK = 1527;
const PRERENDER_SOCIAL_IMAGE = 'social-card.svg';
const SITE_NAME = 'Antigravity Awesome Skills';
const REPOSITORY_URL = 'https://github.com/sickn33/antigravity-awesome-skills';
@ -18,7 +18,7 @@ const FAQ_ITEMS = [
{
question: 'What is Antigravity Awesome Skills?',
answer:
'Antigravity Awesome Skills is an installable GitHub library of 1,525+ reusable SKILL.md playbooks for AI coding assistants. It supports Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and related hosts through direct skill installs, specialized plugins, bundles, workflows, and a searchable catalog.',
'Antigravity Awesome Skills is an installable GitHub library of 1,527+ reusable SKILL.md playbooks for AI coding assistants. It supports Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and related hosts through direct skill installs, specialized plugins, bundles, workflows, and a searchable catalog.',
},
{
question: 'How do I install Antigravity Awesome Skills?',

View File

@ -276,7 +276,7 @@ export function assertIndexDiscoveryMeta(htmlText) {
twitterDescription,
].join(' ');
assert(combined.includes('1,525+'), 'Home SEO metadata must expose the current 1,525+ skill count.');
assert(combined.includes('1,527+'), 'Home SEO metadata must expose the current 1,527+ skill count.');
assert(combined.includes('specialized plugins'), 'Home SEO metadata must mention specialized plugins.');
assert(!combined.includes('prompt templates'), 'Home SEO metadata must not use stale prompt-template positioning.');
assertJsonLdTypes(htmlText, ['CollectionPage', 'Organization', 'WebSite', 'SoftwareSourceCode', 'FAQPage']);
@ -344,7 +344,7 @@ export function assertLlms(llmsText) {
const text = String(llmsText ?? '');
const requiredSnippets = [
'# Antigravity Awesome Skills',
'1,525+',
'1,527+',
'specialized plugins',
'Claude Code',
'Codex CLI',

View File

@ -78,7 +78,7 @@ describe('seo assets verification helpers', () => {
it('requires llms.txt discovery signals', () => {
const llms = `
# Antigravity Awesome Skills
1,525+ agentic skills with specialized plugins for Claude Code and Codex CLI.
1,527+ agentic skills with specialized plugins for Claude Code and Codex CLI.
https://github.com/sickn33/antigravity-awesome-skills
Canonical source of truth: the GitHub repository is the primary project URL.
`;
@ -104,12 +104,12 @@ describe('seo assets verification helpers', () => {
const html = `
<html>
<head>
<title>Antigravity Awesome Skills | 1,525+ AI coding skills and plugins</title>
<meta name="description" content="Explore 1,525+ installable agentic skills, specialized plugins, bundles, and workflows." />
<meta property="og:title" content="Antigravity Awesome Skills | 1,525+ AI coding skills and plugins" />
<meta property="og:description" content="Explore 1,525+ installable agentic skills, specialized plugins, bundles, and workflows." />
<meta name="twitter:title" content="Antigravity Awesome Skills | 1,525+ AI coding skills and plugins" />
<meta name="twitter:description" content="Explore 1,525+ installable agentic skills, specialized plugins, bundles, and workflows." />
<title>Antigravity Awesome Skills | 1,527+ AI coding skills and plugins</title>
<meta name="description" content="Explore 1,527+ installable agentic skills, specialized plugins, bundles, and workflows." />
<meta property="og:title" content="Antigravity Awesome Skills | 1,527+ AI coding skills and plugins" />
<meta property="og:description" content="Explore 1,527+ installable agentic skills, specialized plugins, bundles, and workflows." />
<meta name="twitter:title" content="Antigravity Awesome Skills | 1,527+ AI coding skills and plugins" />
<meta name="twitter:description" content="Explore 1,527+ installable agentic skills, specialized plugins, bundles, and workflows." />
<script type="application/ld+json">
[
{"@context":"https://schema.org","@type":"CollectionPage","sameAs":"https://github.com/sickn33/antigravity-awesome-skills"},

View File

@ -36,7 +36,7 @@ export function Plugins(): React.ReactElement {
Choose the focused AAS plugin for your AI coding workflow
</h1>
<p className="mt-4 max-w-4xl text-sm leading-relaxed text-slate-600 sm:text-base dark:text-slate-300">
AAS specialized plugins are focused, domain-specific distributions of the 1,525+ skill library.
AAS specialized plugins are focused, domain-specific distributions of the 1,527+ skill library.
Start here when you know the job: web apps, security, data analytics, documents, DevOps, QA,
OSS maintenance, mobile apps, automation, or agent and MCP systems.
</p>

View File

@ -10,7 +10,7 @@ const FAQ_ITEMS = [
{
question: 'What is Antigravity Awesome Skills?',
answer:
'Antigravity Awesome Skills is an installable GitHub library of 1,525+ reusable SKILL.md playbooks for AI coding assistants. It supports Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and related hosts through direct skill installs, specialized plugins, bundles, workflows, and a searchable catalog.',
'Antigravity Awesome Skills is an installable GitHub library of 1,527+ reusable SKILL.md playbooks for AI coding assistants. It supports Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and related hosts through direct skill installs, specialized plugins, bundles, workflows, and a searchable catalog.',
},
{
question: 'How do I install Antigravity Awesome Skills?',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -10,6 +10,7 @@
"agentmail",
"agentphone",
"algolia-search",
"android-dev",
"android-jetpack-compose-expert",
"android_ui_verification",
"animejs-animation",
@ -340,6 +341,7 @@
"ui-setup",
"ui-ux-pro-max",
"uniprot-database",
"unship",
"uv-package-manager",
"ux-audit",
"ux-copy",
@ -1246,6 +1248,7 @@
"mobile-core": {
"description": "Mobile app development across native and cross-platform stacks.",
"skills": [
"android-dev",
"android-jetpack-compose-expert",
"android_ui_verification",
"app-store-optimization",

View File

@ -1,6 +1,6 @@
{
"generatedAt": "2026-02-08T00:00:00.000Z",
"total": 1525,
"total": 1527,
"skills": [
{
"id": "00-andruia-consultant",
@ -1606,6 +1606,31 @@
],
"path": "skills/andrej-karpathy/SKILL.md"
},
{
"id": "android-dev",
"name": "android-dev",
"description": "Production-grade Android app development guide covering native (Kotlin/Java), cross-platform (Flutter, RN, KMM), and hybrid architectures.",
"category": "development",
"tags": [
"android",
"dev"
],
"triggers": [
"android",
"dev",
"grade",
"app",
"development",
"covering",
"native",
"kotlin",
"java",
"cross",
"platform",
"flutter"
],
"path": "skills/android-dev/SKILL.md"
},
{
"id": "android-jetpack-compose-expert",
"name": "android-jetpack-compose-expert",
@ -35208,6 +35233,33 @@
],
"path": "skills/unreal-engine-cpp-pro/SKILL.md"
},
{
"id": "unship",
"name": "unship",
"description": "Compare AI agent-made UI variants locally in a real app, then keep one and clean up unused temporary code.",
"category": "data-ai",
"tags": [
"ui-variants",
"frontend",
"local-first",
"coding-agents"
],
"triggers": [
"ui-variants",
"frontend",
"local-first",
"coding-agents",
"unship",
"compare",
"ai",
"agent",
"made",
"ui",
"variants",
"locally"
],
"path": "skills/unship/SKILL.md"
},
{
"id": "unslop",
"name": "unslop",

View File

@ -1221,6 +1221,25 @@
},
"runtime_files": []
},
{
"id": "android-dev",
"path": "skills/android-dev",
"targets": {
"codex": "supported",
"claude": "supported"
},
"setup": {
"type": "none",
"summary": "",
"docs": null
},
"reasons": [],
"blocked_reasons": {
"codex": [],
"claude": []
},
"runtime_files": []
},
{
"id": "android-jetpack-compose-expert",
"path": "skills/android-jetpack-compose-expert",
@ -27223,6 +27242,25 @@
},
"runtime_files": []
},
{
"id": "unship",
"path": "skills/unship",
"targets": {
"codex": "supported",
"claude": "supported"
},
"setup": {
"type": "none",
"summary": "",
"docs": null
},
"reasons": [],
"blocked_reasons": {
"codex": [],
"claude": []
},
"runtime_files": []
},
{
"id": "unslop",
"path": "skills/unslop",
@ -29255,10 +29293,10 @@
}
],
"summary": {
"total_skills": 1525,
"total_skills": 1527,
"supported": {
"codex": 1476,
"claude": 1493
"codex": 1478,
"claude": 1495
},
"blocked": {
"codex": 49,

View File

@ -1439,6 +1439,28 @@
"reasons": []
}
},
{
"id": "android-dev",
"path": "skills/android-dev",
"category": "mobile",
"name": "android-dev",
"description": "Production-grade Android app development guide covering native (Kotlin/Java), cross-platform (Flutter, RN, KMM), and hybrid architectures.",
"risk": "safe",
"source": "community",
"date_added": "2026-06-08",
"plugin": {
"targets": {
"codex": "supported",
"claude": "supported"
},
"setup": {
"type": "none",
"summary": "",
"docs": null
},
"reasons": []
}
},
{
"id": "android-jetpack-compose-expert",
"path": "skills/android-jetpack-compose-expert",
@ -31248,6 +31270,28 @@
"reasons": []
}
},
{
"id": "unship",
"path": "skills/unship",
"category": "development",
"name": "unship",
"description": "Compare AI agent-made UI variants locally in a real app, then keep one and clean up unused temporary code.",
"risk": "safe",
"source": "community",
"date_added": "2026-06-07",
"plugin": {
"targets": {
"codex": "supported",
"claude": "supported"
},
"setup": {
"type": "none",
"summary": "",
"docs": null
},
"reasons": []
}
},
{
"id": "unslop",
"path": "skills/unslop",

View File

@ -1,9 +1,9 @@
---
title: Jetski/Cortex + Gemini Integration Guide
description: "Use antigravity-awesome-skills with Jetski/Cortex without hitting context-window overflow with 1,525+ skills."
description: "Use antigravity-awesome-skills with Jetski/Cortex without hitting context-window overflow with 1,527+ skills."
---
# Jetski/Cortex + Gemini: safe integration with 1,525+ skills
# Jetski/Cortex + Gemini: safe integration with 1,527+ skills
This guide shows how to integrate the `antigravity-awesome-skills` repository with an agent based on **Jetski/Cortex + Gemini** (or similar frameworks) **without exceeding the model context window**.
@ -23,7 +23,7 @@ Never do:
- concatenate all `SKILL.md` content into a single system prompt;
- re-inject the entire library for **every** request.
With 1,525+ skills, this approach fills the context window before user messages are even added, causing truncation.
With 1,527+ skills, this approach fills the context window before user messages are even added, causing truncation.
---

View File

@ -21,7 +21,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,525+ skills installed.
This pattern avoids context overflow when you have 1,527+ skills installed.
Manifest contract references:

View File

@ -6,7 +6,7 @@ This document keeps the repository's GitHub-facing discovery copy aligned with t
Preferred positioning:
> Installable GitHub library of 1,525+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and other AI coding assistants.
> Installable GitHub library of 1,527+ 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,525+ 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,527+ 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,525+ Agentic Skills`;
- use a clean preview image that says `1,527+ Agentic Skills`;
- mention Claude Code, Cursor, Codex CLI, and Gemini CLI;
- avoid dense text and tiny logos that disappear in social cards.

View File

@ -72,7 +72,7 @@ The update process refreshes:
- Canonical skills index (`skills_index.json`)
- Compatibility mirror (`data/skills_index.json`)
- Web app skills data (`apps\web-app\public\skills.json`)
- All 1,525+ skills from the skills directory
- All 1,527+ skills from the skills directory
## When to Update

View File

@ -917,4 +917,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,525+ | Total Bundles: 52_
_Last updated: March 2026 | Total Skills: 1,527+ | Total Bundles: 52_

View File

@ -12,7 +12,7 @@ Install the library into Claude Code, then invoke focused skills directly in the
## Why use this repo for Claude Code
- It includes 1,525+ skills instead of a narrow single-domain starter pack.
- It includes 1,527+ skills instead of a narrow single-domain starter pack.
- It supports the standard `.claude/skills/` path and the Claude Code plugin marketplace flow.
- It also ships generated bundle plugins so teams can install focused packs like `Essentials` or `Security Developer` from the marketplace metadata.
- It includes onboarding docs, bundles, and workflows so new users do not need to guess where to begin.

View File

@ -12,7 +12,7 @@ Install into the Gemini skills path, then ask Gemini to apply one skill at a tim
- 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,525+ files.
- It helps new users get started with bundles and workflows rather than forcing a cold start from 1,527+ 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

View File

@ -1,4 +1,4 @@
# Getting Started with Antigravity Awesome Skills (V12.2.1)
# Getting Started with Antigravity Awesome Skills (V12.3.0)
**New here? This guide will help you supercharge your AI Agent in 5 minutes.**

View File

@ -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,525+ specialized areas
- **Domain expertise** across 1,527+ 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

View File

@ -14,7 +14,7 @@ If you came in through a **Claude Code** or **Codex** plugin instead of a full l
When you ran `npx antigravity-awesome-skills` or cloned the repository, you:
**Downloaded 1,525+ skill files** to your computer (default: `~/.agents/skills/`; or a custom path like `~/.agent/skills/` if you used `--path`)
**Downloaded 1,527+ skill files** to your computer (default: `~/.agents/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)
@ -34,7 +34,7 @@ Bundles are **curated groups** of skills organized by role. They help you decide
**Analogy:**
- You installed a toolbox with 1,525+ tools (✅ done)
- You installed a toolbox with 1,527+ tools (✅ done)
- Bundles are like **labeled organizer trays** saying: "If you're a carpenter, start with these 10 tools"
- You can either **pick skills from the tray** or install that tray as a focused marketplace bundle plugin
@ -212,7 +212,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,525+ skills at once. Here's a sensible approach:
Don't try to use all 1,527+ skills at once. Here's a sensible approach:
If you want a tool-specific starting point before choosing skills, use:
@ -343,7 +343,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,525+ skills installed locally, you should **not** concatenate every `SKILL.md` into a single system prompt or context block.
No. Even though you have 1,527+ skills installed locally, you should **not** concatenate every `SKILL.md` into a single system prompt or context block.
The intended pattern is:

View File

@ -34,7 +34,7 @@ antigravity-awesome-skills/
├── 📄 CONTRIBUTING.md ← Contributor workflow
├── 📄 CATALOG.md ← Full generated catalog
├── 📁 skills/ ← 1,525+ skills live here
├── 📁 skills/ ← 1,527+ skills live here
│ │
│ ├── 📁 brainstorming/
│ │ └── 📄 SKILL.md ← Skill definition
@ -47,7 +47,7 @@ antigravity-awesome-skills/
│ │ └── 📁 2d-games/
│ │ └── 📄 SKILL.md ← Nested skills also supported
│ │
│ └── ... (1,525+ total)
│ └── ... (1,527+ total)
├── 📁 apps/
│ └── 📁 web-app/ ← Interactive browser
@ -100,7 +100,7 @@ antigravity-awesome-skills/
```
┌─────────────────────────┐
│ 1,525+ SKILLS │
│ 1,527+ 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,525+ total) │
│ └── ... (1,527+ total) │
└─────────────────────────────────────────┘
```

View File

@ -1,12 +1,12 @@
{
"name": "antigravity-awesome-skills",
"version": "12.2.1",
"version": "12.3.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "antigravity-awesome-skills",
"version": "12.2.1",
"version": "12.3.0",
"license": "MIT",
"dependencies": {
"yaml": "^2.8.2"

View File

@ -1,7 +1,7 @@
{
"name": "antigravity-awesome-skills",
"version": "12.2.1",
"description": "1,525+ agentic skills for Claude Code, Gemini CLI, Cursor, Antigravity & more. Installer CLI.",
"version": "12.3.0",
"description": "1,527+ 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",

View File

@ -1,7 +1,7 @@
{
"name": "antigravity-awesome-skills",
"version": "12.2.1",
"description": "Plugin-safe Claude Code distribution of Antigravity Awesome Skills with 1,493 supported skills.",
"version": "12.3.0",
"description": "Plugin-safe Claude Code distribution of Antigravity Awesome Skills with 1,495 supported skills.",
"author": {
"name": "sickn33 and contributors",
"url": "https://github.com/sickn33/antigravity-awesome-skills"

View File

@ -450,25 +450,16 @@ python scripts/generate_narration.py --job-id "abc-123-def-456"
# Single speaker, specific voice
python scripts/generate_narration.py --job-id "abc-123-def-456" --voice Aoede
# No speaker intro
python scripts/generate_narration.py --job-id "abc-123-def-456" --no-intro
# Multi-speaker (names required)
python scripts/generate_narration.py --job-id "abc-123-def-456" --multi-speaker \
--speaker1-name "Alice" --speaker2-name "Bob" \
--speaker1-voice Aoede --speaker2-voice Puck
# Multi-speaker mode
python scripts/generate_narration.py --job-id "abc-123-def-456" --multi-speaker
```
**Parameters (aligned with [2slides API](https://2slides.com/api.md)):**
- `--job-id`: Job ID (required, UUID for Nano Banana)
- `--mode`: `single` or `multi` (default: single)
- `--speaker-name`: Speaker name (single mode)
- `--voice`: Voice name (default: Puck); use `--list-voices` for all 30
- `--content-mode`: `concise` or `standard` (default: standard)
- `--no-intro`: Omit speaker introduction (single mode)
- `--speaker1-name`, `--speaker2-name`: Required for multi mode
- `--speaker1-voice`, `--speaker2-voice`: Optional for multi mode
- `--multi-speaker`: Shortcut for `--mode multi`
- `--language`: Narration language (default: Auto)
- `--multi-speaker`: Enable multi-speaker mode
- `--list-voices`: Print the supported voices without calling the API
**Step 3: Check Status**
@ -717,10 +708,9 @@ All scripts accept parameters that match [2slides API](https://2slides.com/api.m
| | `--resolution` | 1K, 2K, 4K |
| | `--content-detail` | concise, standard |
| `create_pdf_slides.py` | Same as above + `--design-style` / `--design-spec` (free text) | |
| `generate_narration.py` | `--mode` | single, multi |
| | `--voice` | 30 voices (Puck, Aoede, Charon, …); use `--list-voices` |
| | `--content-mode` | concise, standard |
| | Multi: `--speaker1-name`, `--speaker2-name`, `--speaker1-voice`, `--speaker2-voice` | |
| `generate_narration.py` | `--voice` | 30 voices (Puck, Aoede, Charon, …); use `--list-voices` |
| | `--language` | Auto, English, Spanish, Arabic, Portuguese, Indonesian, Japanese, Russian, Hindi, French, German, Vietnamese, Turkish, Polish, Italian, Korean, Simplified Chinese, Traditional Chinese |
| | `--multi-speaker` | enabled when present |
| `search_themes.py` | `--query` (required), `--limit` (1100) | |
| `get_job_status.py` | `--job-id` (required) | |
| `download_slides_pages_voices.py` | `--job-id` (required), `--output` (path) | |

View File

@ -34,14 +34,17 @@ npx -y @accesslint/cli@latest "<url>" --port "$PORT" --snapshot accesslint-diff
Branch switching triggers a rebuild but not a browser reload — the CLI opens a fresh tab each time so it always reads the current build. Use `--wait-for "<selector>"` to gate the audit until the rebuild is ready; without it, warn the user that a slow build may yield a stale baseline.
Keep the branch value in the quoted `branch` variable below; never paste or evaluate a branch name as shell syntax.
```bash
git diff --quiet && git diff --cached --quiet || git stash push -u -m "accesslint-diff-branch"
branch="<branch>"
git check-ref-format --branch "$branch" >/dev/null
case "$branch" in -*) echo "Refusing option-like branch name: $branch" >&2; exit 1 ;; esac
git checkout -- "$branch"
git rev-parse --verify --quiet "$branch^{commit}" >/dev/null
git switch "$branch"
npx -y @accesslint/cli@latest "<url>" --port "$PORT" --snapshot accesslint-diff --snapshot-dir /tmp --update-snapshot [--wait-for "<selector>"]
git checkout - && git stash pop 2>/dev/null
git switch - && git stash pop 2>/dev/null
npx -y @accesslint/cli@latest "<url>" --port "$PORT" --snapshot accesslint-diff --snapshot-dir /tmp --format json [--wait-for "<selector>"]
```

View File

@ -0,0 +1,524 @@
---
name: android-dev
description: "Production-grade Android app development guide covering native (Kotlin/Java), cross-platform (Flutter, RN, KMM), and hybrid architectures."
risk: safe
source: community
date_added: "2026-06-08"
---
# Android App Development Skill
## Overview
This skill guides production-grade Android and cross-platform (non-iOS) app development following practices used at big tech companies. It covers the entire development lifecycle — architecture, UI, code quality, testing, error handling, release, and maintenance.
## When to Use This Skill
- Use when deciding on a tech stack (see §1 Stack Selection)
- Use when setting up project architecture (see §2 Architecture)
- Use when designing UI, screens, or a design system (see §3 UI & Design)
- Use when ensuring code quality, patterns, or APIs (see Best Practices)
- Use when implementing error handling or debugging crashes (see §5 Error Handling)
- Use when planning testing strategy (see §6 Testing)
- Use when configuring build, CI/CD, or release pipelines (see §7 Build & Release)
- Use when optimizing performance or memory (see §8 Performance)
- Use when debugging or fixing bugs (see §9 Debugging)
- Use when following the full development roadmap (see §10 Development Roadmap)
- Use when needing deep reference for a stack (see `references/` directory)
---
## §1 Stack Selection
Choose based on team, requirements, and platform targets. **Do not recommend iOS-specific paths.**
### Native Android — Kotlin + Jetpack Compose
**Best for:** Android-only apps, hardware-intensive features, best-in-class UX, new projects.
- Language: **Kotlin**
- UI: **Jetpack Compose** (modern declarative UI)
- Key libs: Room, Retrofit/Ktor, Hilt, WorkManager, DataStore, Navigation Compose
- Reference: `references/native-android.md`
### Native Android — Java + XML Views
**Best for:** Existing Java codebases, teams without Kotlin experience, legacy app maintenance, incremental Kotlin migration.
- Language: **Java** (fully supported by Google, not deprecated)
- UI: **XML Layouts** (ConstraintLayout, RecyclerView, ViewBinding)
- Key libs: Room, Retrofit, Hilt, WorkManager, LiveData, ViewModel
- Java and Kotlin **coexist seamlessly** in the same project — migrate incrementally
- Reference: `references/java-android.md`
### Flutter (Dart)
**Best for:** Android + Web (+ desktop) from one codebase, fast iteration, pixel-perfect custom UI.
- Language: **Dart**
- UI: Flutter Widget tree (Material 3 / Cupertino widgets available but target Material for Android)
- Key libs: Provider/Riverpod/Bloc, Dio, Drift/Isar, go_router, flutter_local_notifications
- Reference: `references/flutter.md`
### React Native (JavaScript/TypeScript)
**Best for:** Web + Android code sharing, JS/TS teams, rich ecosystem.
- Language: **TypeScript** (preferred)
- UI: React Native core components + NativeWind / React Native Paper
- Key libs: React Navigation, Zustand/Redux Toolkit, React Query, MMKV
- Reference: `references/react-native.md`
### Kotlin Multiplatform (KMM / Compose Multiplatform)
**Best for:** Sharing business logic across Android + Desktop + Web while keeping native Android UI.
- Language: **Kotlin** everywhere
- UI: Native Compose on Android; Compose Multiplatform for shared UI
- Key libs: Ktor, SQLDelight, Koin, kotlinx.serialization, Napier
- Reference: `references/kmm.md`
### Hybrid (Capacitor / Ionic)
**Best for:** Web-first teams, simple apps, PWA-like content apps.
- Language: TypeScript + HTML/CSS
- UI: Ionic components or custom web UI
- Avoid for: Heavy animations, native sensor access, high-performance games
- Reference: `references/hybrid.md`
### Decision Matrix
| Requirement | Native Kotlin | Native Java | Flutter | RN | KMM | Hybrid |
|---|---|---|---|---|---|---|
| Android-only (new) | ✅ Best | ✅ | ✅ | ✅ | ✅ | ✅ |
| Android-only (existing Java) | ⚠️ migrate | ✅ Best | ❌ | ❌ | ⚠️ | ❌ |
| Android + Web | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ Best |
| Android + Desktop | ❌ | ❌ | ✅ | ⚠️ | ✅ | ⚠️ |
| Shared business logic only | N/A | N/A | N/A | N/A | ✅ Best | N/A |
| Native performance | ✅ | ✅ | ✅ | ⚠️ | ✅ | ❌ |
| JS/TS team | ❌ | ❌ | ❌ | ✅ Best | ❌ | ✅ |
| Custom pixel-perfect UI | ✅ | ⚠️ | ✅ Best | ⚠️ | ✅ | ❌ |
---
## §2 Architecture
### Core Principle: Separation of Concerns
Every production Android project must separate **UI**, **business logic**, and **data** into distinct, independently testable layers.
### Recommended Architecture: Clean Architecture + MVI/MVVM
```
app/
├── ui/ # Composables / Activities / Fragments / Screen states
├── presentation/ # ViewModels, UI State, UI Events
├── domain/ # Use cases, domain models, repository interfaces
├── data/ # Repository impl, remote (API), local (DB), mappers
└── di/ # Dependency injection modules
```
**Data flow (unidirectional):**
```
User Action → ViewModel/Store → Use Case → Repository → Data Source
UI State (sealed class / StateFlow)
Composable / View renders state
```
### Key Architecture Patterns by Stack
**Native (MVVM + MVI):**
- `StateFlow` / `SharedFlow` for reactive state
- `sealed class UiState` + `sealed class UiEvent`
- Hilt for DI, coroutines + Flow for async
- Repository pattern wrapping Room + Retrofit
**Flutter (BLoC or Riverpod):**
- `Bloc` or `Cubit` for business logic isolation
- `AsyncNotifierProvider` (Riverpod) for data + state
- Repositories as abstract classes with impl injected
**React Native (Redux Toolkit or Zustand):**
- RTK Query or React Query for server state
- Zustand slices for client state
- Custom hooks to encapsulate business logic per feature
**KMM:**
- Shared `commonMain` holds domain + data layers
- `expect/actual` for platform-specific implementations
- Kotlin coroutines + Flow bridged to platform (StateFlow on Android)
### Module Structure (Multi-module for large apps)
```
:app # Entry point, DI wiring
:core:ui # Design system, shared composables
:core:network # API client, interceptors
:core:database # Room / SQLDelight setup
:feature:home
:feature:profile
:feature:settings
```
---
## §3 UI & Design
### Design System First
Before writing screens, define:
1. **Color tokens** — Primary, secondary, surface, on-surface, error; light + dark variants
2. **Typography scale** — Display, headline, title, body, label (Material 3 type system)
3. **Spacing scale** — 4dp grid system (4, 8, 12, 16, 24, 32, 48dp)
4. **Shape tokens** — Corner radii per component family
5. **Component library** — Button, TextField, Card, BottomSheet, TopAppBar, etc.
### Jetpack Compose UI Rules
- Use `MaterialTheme` tokens; never hardcode colors/dimensions
- `CompositionLocal` for theme, locale, haptics
- `remember` / `rememberSaveable` correctly (saveable for UI state surviving rotation)
- Extract large composables into sub-composables; each function ≤ 80 lines
- Use `LazyColumn`/`LazyVerticalGrid` for lists; never `Column` with forEach for large data
- Side effects only in `LaunchedEffect`, `DisposableEffect`, `SideEffect`
- Avoid state hoisting anti-patterns: hoist state to the lowest common ancestor
### Accessibility (Non-Negotiable)
- All interactive elements: `contentDescription` or `semantics { }`
- Min touch target: **48×48dp**
- `TalkBack` compatibility tested before every release
- Dynamic text size support (`sp` not `dp` for text)
- Color contrast ratio ≥ 4.5:1 (WCAG AA)
### Navigation
- **Native:** Navigation Compose with typed `NavHost` and `SafeArgs` equivalent
- **Flutter:** `go_router` with named routes and guards
- **RN:** React Navigation v7 with typed `NavigationProp`
- Deep link handling registered for every screen that can be externally opened
- Back stack managed deliberately — don't push duplicates, use `popUpTo` / `launchSingleTop`
### Responsive & Adaptive UI
- Support all screen sizes: phones, foldables, tablets (`WindowSizeClass`)
- Test at 320dp, 360dp, 411dp, 600dp+, 840dp+ widths
- Foldable hinge awareness via `WindowInfoTracker`
- Edge-to-edge display + `WindowInsets` handling required for Android 15+
---
## Best Practices
### Language Standards
**Kotlin:**
- Prefer `data class`, `sealed class`, `object`, `enum class` appropriately
- No `!!` null assertions — use `?.let`, `?: return`, `requireNotNull` with message
- Coroutines: always specify `CoroutineScope` + `Dispatcher` explicitly; never `GlobalScope`
- Use `@Stable` / `@Immutable` on Compose state classes for smart recomposition
**Java:**
- `@NonNull` / `@Nullable` annotations on every method param and return type
- Never call methods on unchecked objects — null-check explicitly or use `Objects.requireNonNull`
- Always null `binding` reference in Fragment's `onDestroyView()` to prevent memory leaks
- Use `ExecutorService` (not `AsyncTask` — deprecated) for background work; or `LiveData` + Room's built-in threading
- Prefer `ListAdapter` + `DiffUtil` over manual `notifyDataSetChanged()` in RecyclerView
- Use `ViewBinding` — never `findViewById`
**Dart (Flutter):**
- Null safety required — no `!` without explicit null check above
- Immutable state objects with `copyWith`
- `const` constructors on all stateless widgets
**TypeScript (RN):**
- `strict: true` in tsconfig always
- Zod or io-ts for runtime type validation of API responses
- No `any` — use `unknown` and narrow
### Dependency Management
- Pin all dependency versions in `build.gradle.kts` / `pubspec.yaml` / `package.json`
- Audit dependencies monthly for security vulnerabilities
- Avoid transitive dependency conflicts — use dependency resolution strategies
- Keep dependency count minimal — every added lib is a maintenance burden
### Code Review Checklist (PR gate)
- [ ] New public APIs have KDoc / DartDoc / JSDoc
- [ ] No hardcoded strings — use string resources / l10n
- [ ] No hardcoded dimensions or colors outside design tokens
- [ ] No blocking I/O on main thread
- [ ] No memory leaks (no `Activity` context stored in singletons)
- [ ] Coroutine scopes / streams properly cancelled / disposed
- [ ] Feature flag guarding any non-trivial feature
---
## §5 Error Handling
### The Golden Rule
**Never let exceptions propagate to the user silently or crash the app.**
### Error Classification
| Type | Strategy |
|------|----------|
| Network errors | Retry with exponential backoff; show retry UI |
| Auth errors (401/403) | Refresh token → re-request → logout if fails |
| Validation errors | Show inline field errors immediately |
| Data parsing errors | Log + fallback to cached/default state |
| Unexpected crashes | Catch at top-level; show error screen + report |
| Background task failures | Retry via WorkManager; notify user if critical |
### Result / Either Pattern (Kotlin)
```kotlin
sealed class AppResult<out T> {
data class Success<T>(val data: T) : AppResult<T>()
data class Error(val exception: AppException) : AppResult<Nothing>()
}
sealed class AppException(msg: String) : Exception(msg) {
class NetworkException(msg: String) : AppException(msg)
class AuthException(msg: String) : AppException(msg)
class ParseException(msg: String) : AppException(msg)
class UnknownException(msg: String) : AppException(msg)
}
```
Use `AppResult<T>` as return type for all repository + use case functions. ViewModels map to `UiState.Error`.
### Crash Reporting
- Integrate **Firebase Crashlytics** or **Sentry** from day one
- Set user identifiers and custom keys before crash occurs
- Non-fatal exceptions logged for all caught errors
- ANR monitoring enabled
- Crash-free sessions target: **≥ 99.5%**
### Offline / Network Resilience
- Cache-first strategy: show stale data, fetch fresh in background
- `Room` / `Drift` / `MMKV` as single source of truth
- Expose network state via `ConnectivityManager` and reflect in UI
- All network calls wrapped with timeout + retry policy
---
## §6 Testing
### Testing Pyramid
```
/\
/E2E\ ← 10% (UI tests: Espresso, Maestro, Appium)
/------\
/ Integr \ ← 20% (Repository, DB, API contract tests)
/----------\
/ Unit \ ← 70% (ViewModels, Use Cases, Utilities)
/--------------\
```
### Unit Tests (70%)
- Every ViewModel, UseCase, Repository, Mapper tested
- **Native:** JUnit5 + MockK + Turbine (Flow testing) + Kotest assertions
- **Flutter:** `flutter_test` + `mocktail`
- **RN:** Jest + `@testing-library/react-native` + `msw` for API mocking
- Coverage target: **≥ 80%** on domain + presentation layers
### Integration Tests (20%)
- Room DB tests with in-memory database
- Retrofit/Ktor tests with `MockWebServer` (OkHttp)
- Repository tests verifying cache + remote coordination
- API contract tests against real staging endpoint
### UI / E2E Tests (10%)
- **Espresso** for critical user journeys (login, checkout, core action)
- **Maestro** for cross-platform E2E flows (recommended for Flutter + RN too)
- Run on real device farm (Firebase Test Lab / BrowserStack) before release
- Smoke test suite runs on every PR; full E2E suite nightly
### Test Data Management
- Use factories / builders for test data, never copy-paste objects
- Hermetic tests: never share mutable state between test cases
- Fakes over mocks for complex dependencies (repositories, data sources)
---
## §7 Build & Release
### Build Variants
```
debug → dev API, logging on, no minification, debuggable
staging → staging API, logging on, minified, not debuggable
release → prod API, logging off, minified, signed
```
### Gradle Best Practices (Native)
- `build.gradle.kts` only — no Groovy DSL in new projects
- Version catalog (`libs.versions.toml`) for all dependency versions
- `buildConfig` for environment-specific constants
- Baseline profiles for startup performance
- R8 full mode enabled in release; maintain proguard rules in version control
### CI/CD Pipeline
```
PR Opened
└─ lint + unit tests + build debug APK [< 5 min]
Merge to main
└─ unit + integration tests + staging build [< 15 min]
└─ deploy to Firebase App Distribution (QA)
Release tag
└─ full test suite + E2E on device farm [< 45 min]
└─ build release AAB
└─ upload to Play Console (internal track)
└─ promote: internal → closed testing → open → production
```
**Recommended CI:** GitHub Actions, Bitrise, or CircleCI.
### Play Store Release Strategy
- Always release to **internal → closed → open testing** before production
- Use **staged rollouts**: 5% → 20% → 50% → 100% with 24-48h monitoring
- Monitor Crashlytics + ANR rate + rating before expanding rollout
- **Never skip staged rollout** for significant changes
### App Signing
- Upload key (Play App Signing): stored in CI secrets, never committed
- Use Google Play App Signing for distribution key management
- Document key recovery procedure in team runbook
---
## §8 Performance
### Startup Performance
- App startup time target: **cold start < 1s**, warm start < 500ms
- Use **App Startup library** for initializing libraries lazily
- Baseline profiles generated + committed to repo
- Heavy initialization moved off main thread
### UI Performance
- Target: **60fps** (90/120fps on supported devices); **zero jank**
- Measure with **Android Studio Profiler** + `FrameMetrics` API
- Avoid allocation in `draw()` / `onMeasure()` / composition
- Use `derivedStateOf` in Compose to avoid unnecessary recompositions
- Image loading: Coil (Compose) / Glide / Picasso — never load full-res in thumbnails
### Memory
- No `Activity` / `Context` references in ViewModels or singletons
- WeakReferences for listeners stored beyond their owner's lifecycle
- Bitmap recycling and memory cache sizing
- Heap dump + leak detection via **LeakCanary** in debug builds (always)
### Network
- HTTP caching headers respected
- Image CDN + WebP format
- Gzip/Brotli compression verified
- Request batching where applicable
- Connection pooling configured
### Battery
- Background work only via **WorkManager** with appropriate constraints
- Location updates: request only needed accuracy level; stop when backgrounded
- Wakelocks used sparingly with explicit release
---
## §9 Debugging & Bug Fixing
### Debugging Process
1. **Reproduce reliably** — document exact steps, device, OS version, account state
2. **Isolate** — is it UI, business logic, network, or persistence?
3. **Instrument** — add targeted logs / breakpoints, NOT shotgun logging
4. **Hypothesize** — form 1-3 specific hypotheses before touching code
5. **Fix the root cause** — never patch symptoms; trace back to the source
6. **Regression test** — write a test that fails before fix, passes after
7. **Document** — comment explaining why the fix works, not just what it does
### Common Android Bug Patterns
| Bug | Likely Cause | Fix |
|-----|-------------|-----|
| ANR | Main thread I/O / long computation | Move to coroutine/Dispatcher.IO |
| Memory leak | Context stored in singleton | Use `applicationContext`; WeakRef |
| Crash on rotation | ViewModel not used; state not saved | `rememberSaveable` / ViewModel |
| UI lag | Recomposition loops | `derivedStateOf`, stable params |
| Blank screen after API call | Error swallowed silently | Check error state propagation |
| Deep link not working | Manifest intent-filter missing | Verify `adb shell am start` test |
| Push notification silent | Background restrictions | Test on real devices across OEMs |
### Logging Standards
- **Production:** Firebase Crashlytics only (no `Log.d` in release builds)
- **Debug/Staging:** Timber with debug tree
- Log levels: ERROR (crashes), WARN (recoverable), INFO (key events), DEBUG (dev only)
- Never log PII — mask emails, phone numbers, tokens in logs
### OEM-Specific Issues
- Test on **Samsung**, **Xiaomi/MIUI**, **OnePlus/OxygenOS**, **Huawei (no GMS)** for critical flows
- Background restrictions vary widely by OEM — test push, alarms, background sync
- Maintain a physical or cloud device farm with top market-share devices
---
## §10 Development Roadmap
Follow this phase structure for any new Android project:
### Phase 0 — Foundation (Week 1-2)
- [ ] Stack decision documented with rationale
- [ ] Module structure defined
- [ ] Design system tokens defined (colors, type, spacing, shapes)
- [ ] CI pipeline running (lint + unit tests + build)
- [ ] Crash reporting integrated (Crashlytics/Sentry)
- [ ] Analytics baseline integrated (Firebase/Amplitude)
- [ ] API contract / mock server set up
- [ ] DI framework configured
- [ ] Navigation skeleton implemented
- [ ] Flavor/build variant config complete
### Phase 1 — Core Features (Weeks 3-8)
- [ ] Auth flow (login, register, token refresh, logout)
- [ ] Core screen shells with real navigation
- [ ] Network layer (client, interceptors, error handling)
- [ ] Local persistence layer (DB schema + DAOs)
- [ ] Repository layer wiring remote + local
- [ ] ViewModels + UI states for each feature
- [ ] Unit tests for all ViewModels + use cases
- [ ] Feature flags infrastructure
### Phase 2 — Polish (Weeks 9-12)
- [ ] Design QA pass against Figma/spec
- [ ] Accessibility audit (TalkBack, contrast, touch targets)
- [ ] Dark mode implementation + verification
- [ ] Localization (strings externalized, RTL support if needed)
- [ ] Loading, empty, error states on every screen
- [ ] Deep link handling
- [ ] Widget / notification implementation
- [ ] Offline mode verification
### Phase 3 — Hardening (Weeks 12-14)
- [ ] Performance profiling (startup, scroll, memory)
- [ ] E2E test suite on device farm (Firebase Test Lab)
- [ ] Security review (certificate pinning, biometrics, secure storage)
- [ ] Proguard / R8 rules verified
- [ ] Crash-free rate ≥ 99.5% on staging
- [ ] Play Store listing, screenshots, privacy policy
### Phase 4 — Release
- [ ] AAB signed and uploaded to internal track
- [ ] Staged rollout plan defined
- [ ] Monitoring dashboard set up (Crashlytics, Play Console vitals)
- [ ] Rollback plan documented
- [ ] On-call rotation assigned
### Phase 5 — Post-Launch (Ongoing)
- Crash-free rate monitored daily
- ANR rate < 0.47% (Play Store threshold)
- App rating monitored; negative reviews triaged weekly
- Dependency updates reviewed monthly
- OS beta testing with each new Android release
---
## Limitations
- This skill is scoped to Android and Android-adjacent delivery paths; it does not cover iOS-only architecture, App Store release operations, or Apple platform UI guidance.
- Version numbers, Play Console policy thresholds, and recommended libraries can change; verify release-critical details against current Android, Google Play, and library documentation before shipping.
- Code snippets are architecture patterns, not complete applications; adapt package names, dependency versions, permissions, privacy disclosures, and security controls to the actual project.
- The guidance does not replace device QA, accessibility review, security review, legal/privacy review, or store compliance checks for a production release.
## Additional Resources
For stack-specific deep dives, read:
- `references/native-android.md` — Kotlin, Compose, Room, Hilt, Coroutines
- `references/java-android.md` — Java, XML Views, ViewBinding, LiveData, Retrofit, Room, Hilt, migration path
- `references/flutter.md` — Dart, BLoC/Riverpod, Drift, go_router
- `references/react-native.md` — TypeScript, RN architecture, Hermes, New Architecture
- `references/kmm.md` — KMM shared modules, SQLDelight, Ktor, Compose Multiplatform
- `references/hybrid.md` — Capacitor, Ionic, PWA considerations

View File

@ -0,0 +1,269 @@
# Flutter Reference (Dart)
## Project Structure
```
lib/
├── main.dart # Entry point
├── app/
│ ├── app.dart # MaterialApp + router setup
│ ├── theme/ # ThemeData, colors, typography, spacing
│ └── router/ # go_router config, guards
├── features/
│ └── home/
│ ├── data/
│ │ ├── datasource/ # Remote + local data sources
│ │ ├── dto/ # JSON models (freezed)
│ │ └── repository/ # Repo implementations
│ ├── domain/
│ │ ├── model/ # Domain models (freezed)
│ │ ├── repository/ # Abstract repo interfaces
│ │ └── usecase/ # Use cases
│ └── presentation/
│ ├── bloc/ # Bloc/Cubit + state + event
│ └── screen/ # Widgets + page files
├── core/
│ ├── network/ # Dio client, interceptors
│ ├── database/ # Drift DB setup
│ ├── widgets/ # Shared design system widgets
│ └── error/ # Failure types, error handling
└── injection.dart # GetIt service locator setup
```
## State Management (BLoC)
```dart
// States
@freezed
class HomeState with _$HomeState {
const factory HomeState.initial() = _Initial;
const factory HomeState.loading() = _Loading;
const factory HomeState.success(List<Item> items) = _Success;
const factory HomeState.failure(String message) = _Failure;
}
// Events
@freezed
class HomeEvent with _$HomeEvent {
const factory HomeEvent.loadItems() = _LoadItems;
const factory HomeEvent.refreshItems() = _RefreshItems;
}
// Bloc
class HomeBloc extends Bloc<HomeEvent, HomeState> {
final GetItemsUseCase _getItems;
HomeBloc(this._getItems) : super(const HomeState.initial()) {
on<_LoadItems>(_onLoad);
}
Future<void> _onLoad(_LoadItems event, Emitter<HomeState> emit) async {
emit(const HomeState.loading());
final result = await _getItems();
result.fold(
(failure) => emit(HomeState.failure(failure.message)),
(items) => emit(HomeState.success(items)),
);
}
}
```
## State Management (Riverpod — alternative)
```dart
@riverpod
class HomeNotifier extends _$HomeNotifier {
@override
FutureOr<List<Item>> build() => _load();
Future<List<Item>> _load() async {
final repo = ref.read(itemRepositoryProvider);
return repo.getItems().getOrThrow();
}
Future<void> refresh() async {
state = const AsyncLoading();
state = await AsyncValue.guard(_load);
}
}
```
## Screen Widget Pattern
```dart
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (ctx) => sl<HomeBloc>()..add(const HomeEvent.loadItems()),
child: const _HomeView(),
);
}
}
class _HomeView extends StatelessWidget {
const _HomeView();
@override
Widget build(BuildContext context) {
return Scaffold(
body: BlocConsumer<HomeBloc, HomeState>(
listener: (ctx, state) {
state.maybeWhen(
failure: (msg) => ScaffoldMessenger.of(ctx)
.showSnackBar(SnackBar(content: Text(msg))),
orElse: () {},
);
},
builder: (ctx, state) => state.when(
initial: () => const SizedBox(),
loading: () => const Center(child: CircularProgressIndicator()),
success: (items) => _ItemList(items: items),
failure: (msg) => ErrorView(message: msg,
onRetry: () => ctx.read<HomeBloc>().add(
const HomeEvent.loadItems())),
),
),
);
}
}
```
## go_router Setup
```dart
final router = GoRouter(
initialLocation: '/home',
redirect: (context, state) {
final isLoggedIn = ref.read(authStateProvider).isLoggedIn;
if (!isLoggedIn && !state.matchedLocation.startsWith('/auth')) {
return '/auth/login';
}
return null;
},
routes: [
GoRoute(
path: '/home',
name: AppRoutes.home,
builder: (ctx, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'detail/:id',
builder: (ctx, state) =>
DetailScreen(id: state.pathParameters['id']!),
),
],
),
],
);
```
## Drift Database
```dart
@DriftDatabase(tables: [Items])
class AppDatabase extends _$AppDatabase {
AppDatabase(QueryExecutor e) : super(e);
@override
int get schemaVersion => 1;
Stream<List<Item>> watchAllItems() =>
(select(items)..orderBy([(t) => OrderingTerm.desc(t.updatedAt)])).watch();
Future<void> upsertItems(List<ItemsCompanion> rows) =>
batch((b) => b.insertAllOnConflictUpdate(items, rows));
}
```
## Key pubspec.yaml Dependencies
```yaml
dependencies:
flutter_bloc: ^8.1.5
freezed_annotation: ^2.4.1
riverpod: ^2.5.1 # alternative to bloc
flutter_riverpod: ^2.5.1
go_router: ^14.1.0
dio: ^5.4.3
drift: ^2.18.0
sqflite: ^2.3.3
get_it: ^7.7.0
injectable: ^2.4.1
dartz: ^0.10.1 # Either/Option for FP error handling
json_annotation: ^4.9.0
dev_dependencies:
build_runner: ^2.4.9
freezed: ^2.5.2
json_serializable: ^6.8.0
drift_dev: ^2.18.0
mocktail: ^1.0.3
bloc_test: ^9.1.7
```
## Error Handling (Either/Failure pattern)
```dart
abstract class Failure {
final String message;
const Failure(this.message);
}
class NetworkFailure extends Failure {
const NetworkFailure([super.message = 'Network error occurred']);
}
class CacheFailure extends Failure {
const CacheFailure([super.message = 'Cache error occurred']);
}
// Repository
Future<Either<Failure, List<Item>>> getItems() async {
try {
final remote = await _remoteSource.fetchItems();
await _localSource.saveItems(remote);
return Right(remote.map(_mapper.toDomain).toList());
} on DioException catch (e) {
return Left(NetworkFailure(e.message ?? 'Network error'));
} on Exception {
return const Left(CacheFailure());
}
}
```
## Testing
```dart
void main() {
group('HomeBloc', () {
late HomeBloc bloc;
late MockGetItemsUseCase mockUseCase;
setUp(() {
mockUseCase = MockGetItemsUseCase();
bloc = HomeBloc(mockUseCase);
});
tearDown(() => bloc.close());
blocTest<HomeBloc, HomeState>(
'emits [loading, success] when loadItems succeeds',
build: () {
when(() => mockUseCase()).thenAnswer(
(_) async => Right([Item(id: '1', title: 'Test')]),
);
return bloc;
},
act: (b) => b.add(const HomeEvent.loadItems()),
expect: () => [
const HomeState.loading(),
isA<HomeState>().having((s) => s, 'success',
const HomeState.success([Item(id: '1', title: 'Test')])),
],
);
});
}
```

View File

@ -0,0 +1,158 @@
# Hybrid Android Reference (Capacitor + Ionic / React)
## When to Use Hybrid
✅ Good fit:
- Web team building a companion Android app
- Content-heavy apps (news, docs, forms)
- PWA upgrade to installable app
- Rapid prototyping
❌ Avoid for:
- Real-time games / heavy animations
- Deep native sensor / hardware access
- Apps requiring 60fps custom animations
- Bluetooth/NFC intensive apps (use plugins, but complex)
## Stack Options
| Option | UI Framework | Best For |
|--------|-------------|---------|
| Capacitor + Ionic | Ionic components | Full mobile-optimized UI |
| Capacitor + React | React + Tailwind | Web team reuse |
| Capacitor + Vue | Vue + Ionic | Vue teams |
| Capacitor + Angular | Angular + Ionic | Enterprise Angular teams |
## Project Structure (Capacitor + React)
```
src/
├── App.tsx
├── pages/ # Screen components
├── components/ # Shared UI components
├── hooks/ # Business logic hooks
├── services/ # API, storage services
└── store/ # State management
android/ # Native Android project (generated)
├── app/src/main/
│ ├── AndroidManifest.xml
│ └── java/.../MainActivity.kt
capacitor.config.ts # Capacitor configuration
```
## Capacitor Config
```typescript
// capacitor.config.ts
import { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.example.app',
appName: 'My App',
webDir: 'dist',
server: {
androidScheme: 'https',
},
android: {
buildOptions: {
releaseType: 'APK', // or AAB for Play Store
},
},
plugins: {
SplashScreen: {
launchShowDuration: 0,
backgroundColor: '#FFFFFF',
},
PushNotifications: {
presentationOptions: ['badge', 'sound', 'alert'],
},
},
};
```
## Native Plugin Usage
```typescript
import { Camera, CameraResultType } from '@capacitor/camera';
import { Preferences } from '@capacitor/preferences';
import { PushNotifications } from '@capacitor/push-notifications';
import { Geolocation } from '@capacitor/geolocation';
// Camera
const takePhoto = async () => {
const photo = await Camera.getPhoto({
quality: 90,
allowEditing: false,
resultType: CameraResultType.Uri,
});
return photo.webPath;
};
// Secure storage
const saveToken = async (token: string) => {
await Preferences.set({ key: 'auth_token', value: token });
};
const getToken = async (): Promise<string | null> => {
const { value } = await Preferences.get({ key: 'auth_token' });
return value;
};
// Push notifications
const initPush = async () => {
const permission = await PushNotifications.requestPermissions();
if (permission.receive === 'granted') {
await PushNotifications.register();
}
PushNotifications.addListener('registration', ({ value: token }) => {
console.log('FCM Token:', token);
});
};
```
## Performance Best Practices
- Ensure hardware acceleration is enabled for the application in AndroidManifest.xml (default in Capacitor)
- Enable HTTP caching in Android WebView settings
- Lazy-load routes with React.lazy / dynamic imports
- Avoid `setTimeout`/`setInterval` for animations; use CSS transitions
- Use `@ionic/react` components — they handle mobile-specific touch handling
- Ionic virtual scroll for long lists
## Build & Deploy
```bash
# Build web assets
npm run build
# Sync to native
npx cap sync android
# Open in Android Studio
npx cap open android
# Build release APK/AAB via Android Studio or:
cd android && ./gradlew bundleRelease
```
## Custom Native Plugin (when built-in plugins don't cover it)
```kotlin
// android/app/src/main/java/.../MyPlugin.kt
@CapacitorPlugin(name = "MyPlugin")
class MyPlugin : Plugin() {
@PluginMethod
fun doNativeWork(call: PluginCall) {
val value = call.getString("input") ?: return call.reject("No input")
// Do native work
val result = JSObject()
result.put("output", "processed: $value")
call.resolve(result)
}
}
// TypeScript usage
import { registerPlugin } from '@capacitor/core';
const MyPlugin = registerPlugin<{ doNativeWork: (opts: { input: string }) => Promise<{ output: string }> }>('MyPlugin');
const result = await MyPlugin.doNativeWork({ input: 'hello' });
```

View File

@ -0,0 +1,586 @@
# Native Android — Java Reference
## When to Use Java
Java remains fully supported by Android and Google. Use it when:
- Maintaining or extending an existing Java codebase
- Team is Java-fluent without Kotlin experience
- Integrating Java-only SDKs or legacy modules
- Gradual migration: new Kotlin modules alongside old Java modules
> **Java + Kotlin interop is seamless** — you can have both in the same project. New files can be Kotlin while legacy files stay Java.
---
## Project Structure
```
app/src/main/java/com/example/app/
├── MyApp.java # Application class
├── MainActivity.java # Host activity
├── ui/
│ └── home/
│ ├── HomeActivity.java # OR Fragment-based
│ ├── HomeFragment.java
│ └── HomeAdapter.java
├── viewmodel/
│ └── HomeViewModel.java
├── repository/
│ └── ItemRepository.java
├── data/
│ ├── remote/
│ │ ├── ApiService.java # Retrofit interface
│ │ ├── ApiClient.java # OkHttp + Retrofit setup
│ │ └── dto/ItemDto.java
│ └── local/
│ ├── AppDatabase.java # Room database
│ ├── ItemDao.java
│ └── entity/ItemEntity.java
├── model/
│ └── Item.java # Domain model
└── di/ # Manual DI or Hilt
```
---
## ViewModel (Java + LiveData)
```java
public class HomeViewModel extends ViewModel {
private final MutableLiveData<UiState<List<Item>>> _uiState =
new MutableLiveData<>(UiState.loading());
public LiveData<UiState<List<Item>>> uiState = _uiState;
private final ItemRepository repository;
private final ExecutorService executor = Executors.newSingleThreadExecutor();
// Constructor injection (Hilt or manual)
public HomeViewModel(ItemRepository repository) {
this.repository = repository;
loadItems();
}
public void loadItems() {
_uiState.setValue(UiState.loading());
executor.execute(() -> {
try {
List<Item> items = repository.getItems();
_uiState.postValue(UiState.success(items));
} catch (Exception e) {
_uiState.postValue(UiState.error(e.getMessage()));
}
});
}
@Override
protected void onCleared() {
super.onCleared();
executor.shutdown();
}
}
```
---
## UiState Wrapper
```java
public class UiState<T> {
public enum Status { LOADING, SUCCESS, ERROR }
public final Status status;
public final T data;
public final String errorMessage;
private UiState(Status status, T data, String errorMessage) {
this.status = status;
this.data = data;
this.errorMessage = errorMessage;
}
public static <T> UiState<T> loading() {
return new UiState<>(Status.LOADING, null, null);
}
public static <T> UiState<T> success(T data) {
return new UiState<>(Status.SUCCESS, data, null);
}
public static <T> UiState<T> error(String message) {
return new UiState<>(Status.ERROR, null, message);
}
public boolean isLoading() { return status == Status.LOADING; }
public boolean isSuccess() { return status == Status.SUCCESS; }
public boolean isError() { return status == Status.ERROR; }
}
```
---
## Fragment Observing ViewModel
```java
public class HomeFragment extends Fragment {
private HomeViewModel viewModel;
private FragmentHomeBinding binding; // ViewBinding
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
binding = FragmentHomeBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
viewModel = new ViewModelProvider(this,
new HomeViewModelFactory(new ItemRepository(requireContext())))
.get(HomeViewModel.class);
viewModel.uiState.observe(getViewLifecycleOwner(), state -> {
binding.progressBar.setVisibility(state.isLoading() ? View.VISIBLE : View.GONE);
binding.recyclerView.setVisibility(state.isSuccess() ? View.VISIBLE : View.GONE);
binding.errorView.setVisibility(state.isError() ? View.VISIBLE : View.GONE);
if (state.isSuccess()) {
adapter.submitList(state.data);
}
if (state.isError()) {
binding.errorText.setText(state.errorMessage);
}
});
binding.retryButton.setOnClickListener(v -> viewModel.loadItems());
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null; // CRITICAL — avoid memory leak
}
}
```
---
## Room Database (Java)
```java
// Entity
@Entity(tableName = "items")
public class ItemEntity {
@PrimaryKey
@NonNull
public String id;
public String title;
public long updatedAt;
public ItemEntity(@NonNull String id, String title, long updatedAt) {
this.id = id;
this.title = title;
this.updatedAt = updatedAt;
}
}
// DAO
@Dao
public interface ItemDao {
@Query("SELECT * FROM items ORDER BY updatedAt DESC")
LiveData<List<ItemEntity>> observeAll();
@Query("SELECT * FROM items ORDER BY updatedAt DESC")
List<ItemEntity> getAll(); // blocking — call off main thread
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAll(List<ItemEntity> items);
@Query("DELETE FROM items")
void deleteAll();
}
// Database
@Database(entities = {ItemEntity.class}, version = 1, exportSchema = true)
public abstract class AppDatabase extends RoomDatabase {
private static volatile AppDatabase INSTANCE;
public abstract ItemDao itemDao();
public static AppDatabase getInstance(Context context) {
if (INSTANCE == null) {
synchronized (AppDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context.getApplicationContext(),
AppDatabase.class,
"app_database"
).build();
}
}
}
return INSTANCE;
}
}
```
---
## Retrofit API Client (Java)
```java
// Interface
public interface ApiService {
@GET("items")
Call<List<ItemDto>> getItems();
@GET("items/{id}")
Call<ItemDto> getItemById(@Path("id") String id);
@POST("items")
Call<ItemDto> createItem(@Body ItemDto item);
}
// Client setup
public class ApiClient {
private static final String BASE_URL = BuildConfig.API_BASE_URL;
private static ApiService INSTANCE;
public static ApiService getInstance() {
if (INSTANCE == null) {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.addInterceptor(new AuthInterceptor())
.addInterceptor(new HttpLoggingInterceptor()
.setLevel(BuildConfig.DEBUG
? HttpLoggingInterceptor.Level.BODY
: HttpLoggingInterceptor.Level.NONE))
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
INSTANCE = retrofit.create(ApiService.class);
}
return INSTANCE;
}
}
// Auth interceptor
public class AuthInterceptor implements Interceptor {
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
String token = TokenStorage.getInstance().getToken();
Request request = chain.request().newBuilder()
.addHeader("Authorization", "Bearer " + token)
.build();
return chain.proceed(request);
}
}
```
---
## Repository (Java)
```java
public class ItemRepository {
private final ItemDao itemDao;
private final ApiService apiService;
private final ExecutorService executor = Executors.newSingleThreadExecutor();
public ItemRepository(Context context) {
AppDatabase db = AppDatabase.getInstance(context);
this.itemDao = db.itemDao();
this.apiService = ApiClient.getInstance();
}
// Synchronous fetch for ViewModel executor
public List<Item> getItems() throws Exception {
Response<List<ItemDto>> response = apiService.getItems().execute();
if (response.isSuccessful() && response.body() != null) {
return response.body().stream()
.map(ItemMapper::toDomain)
.collect(Collectors.toList());
} else {
throw new IOException("HTTP " + response.code());
}
}
// Observe cached data (returns LiveData — auto updates UI)
public LiveData<List<Item>> observeItems() {
return Transformations.map(itemDao.observeAll(), entities ->
entities.stream().map(ItemMapper::toDomain).collect(Collectors.toList())
);
}
// Refresh from network (call from background thread or executor)
public void refreshItems(Callback<Void> callback) {
executor.execute(() -> {
try {
Response<List<ItemDto>> response = apiService.getItems().execute();
if (response.isSuccessful() && response.body() != null) {
List<ItemEntity> entities = response.body().stream()
.map(ItemMapper::toEntity)
.collect(Collectors.toList());
itemDao.deleteAll();
itemDao.insertAll(entities);
callback.onSuccess(null);
} else {
callback.onError(new IOException("HTTP " + response.code()));
}
} catch (IOException e) {
callback.onError(e);
}
});
}
public interface Callback<T> {
void onSuccess(T result);
void onError(Exception e);
}
}
```
---
## RecyclerView Adapter (Java)
```java
public class ItemAdapter extends ListAdapter<Item, ItemAdapter.ItemViewHolder> {
private final OnItemClickListener listener;
public interface OnItemClickListener {
void onItemClick(Item item);
}
public ItemAdapter(OnItemClickListener listener) {
super(new DiffUtil.ItemCallback<Item>() {
@Override
public boolean areItemsTheSame(@NonNull Item a, @NonNull Item b) {
return a.getId().equals(b.getId());
}
@Override
public boolean areContentsTheSame(@NonNull Item a, @NonNull Item b) {
return a.equals(b);
}
});
this.listener = listener;
}
@NonNull
@Override
public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemRowBinding binding = ItemRowBinding.inflate(
LayoutInflater.from(parent.getContext()), parent, false);
return new ItemViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) {
holder.bind(getItem(position), listener);
}
static class ItemViewHolder extends RecyclerView.ViewHolder {
private final ItemRowBinding binding;
ItemViewHolder(ItemRowBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
void bind(Item item, OnItemClickListener listener) {
binding.titleText.setText(item.getTitle());
binding.getRoot().setOnClickListener(v -> listener.onItemClick(item));
}
}
}
```
---
## XML Layout Best Practices (Java projects)
```xml
<!-- Use ConstraintLayout — flat hierarchy = better performance -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Always use ?attr/ tokens from MaterialTheme, never hardcoded colors -->
<TextView
android:id="@+id/titleText"
android:textColor="?attr/colorOnSurface"
android:textAppearance="?attr/textAppearanceTitleMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
```
- Always use **ViewBinding** (not `findViewById`, not DataBinding for simple cases)
- Enable in `build.gradle.kts`: `viewBinding { enable = true }`
- Null `binding` in `onDestroyView()` to prevent Fragment memory leaks
---
## Error Handling (Java)
```java
// Checked exceptions: always handle explicitly
public Result<List<Item>> getItemsSafe() {
try {
Response<List<ItemDto>> response = apiService.getItems().execute();
if (!response.isSuccessful()) {
return Result.failure(new HttpException(response));
}
List<Item> items = Objects.requireNonNull(response.body())
.stream().map(ItemMapper::toDomain).collect(Collectors.toList());
return Result.success(items);
} catch (IOException e) {
return Result.failure(new NetworkException("Network error", e));
} catch (NullPointerException e) {
return Result.failure(new ParseException("Empty response body", e));
}
}
// Custom exception hierarchy
public class AppException extends Exception {
public AppException(String message) { super(message); }
public AppException(String message, Throwable cause) { super(message, cause); }
}
public class NetworkException extends AppException { ... }
public class ParseException extends AppException { ... }
public class AuthException extends AppException { ... }
```
---
## Hilt DI (Java)
```java
// Application
@HiltAndroidApp
public class MyApp extends Application {}
// Activity / Fragment — annotate for injection
@AndroidEntryPoint
public class HomeFragment extends Fragment {
@Inject
ItemRepository repository; // injected by Hilt
}
// ViewModel
@HiltViewModel
public class HomeViewModel extends ViewModel {
private final ItemRepository repository;
@Inject
public HomeViewModel(ItemRepository repository) {
this.repository = repository;
}
}
// Module
@Module
@InstallIn(SingletonComponent.class)
public class DatabaseModule {
@Provides
@Singleton
public AppDatabase provideDatabase(@ApplicationContext Context context) {
return AppDatabase.getInstance(context);
}
@Provides
public ItemDao provideItemDao(AppDatabase db) {
return db.itemDao();
}
}
```
---
## Unit Testing (Java)
```java
@ExtendWith(MockitoExtension.class)
class HomeViewModelTest {
@Mock
ItemRepository mockRepository;
HomeViewModel viewModel;
@BeforeEach
void setup() {
viewModel = new HomeViewModel(mockRepository);
}
@Test
void loadItems_success_emitsSuccessState() throws Exception {
List<Item> items = Arrays.asList(new Item("1", "Test"));
when(mockRepository.getItems()).thenReturn(items);
viewModel.loadItems();
// Wait for executor — use CountDownLatch or InstantExecutorRule
UiState<List<Item>> state = viewModel.uiState.getValue();
assertNotNull(state);
assertTrue(state.isSuccess());
assertEquals(items, state.data);
}
@Test
void loadItems_failure_emitsErrorState() throws Exception {
when(mockRepository.getItems()).thenThrow(new IOException("Network error"));
viewModel.loadItems();
UiState<List<Item>> state = viewModel.uiState.getValue();
assertNotNull(state);
assertTrue(state.isError());
}
}
```
---
## Java → Kotlin Migration Path
When migrating a Java project to Kotlin incrementally:
1. **New files in Kotlin** — Java and Kotlin coexist seamlessly
2. **Convert utilities first**`@JvmStatic`, `@JvmField` for interop
3. **Convert data models** — Java POJOs → Kotlin `data class`
4. **Convert DAOs and Repositories** — add `suspend` + `Flow`
5. **Convert ViewModels last** — swap `LiveData` + `MutableLiveData` for `StateFlow`
6. **Convert Activities/Fragments** — migrate to Compose screen by screen
7. Annotate Kotlin with `@JvmOverloads`, `@JvmName` where Java callers exist
```kotlin
// Kotlin data class replacing a Java POJO
data class Item(
val id: String,
val title: String,
val updatedAt: Long = System.currentTimeMillis()
)
// Kotlin extension to consume Java LiveData from Kotlin cleanly
fun <T> LiveData<T>.observeNonNull(owner: LifecycleOwner, observer: (T) -> Unit) {
observe(owner) { it?.let(observer) }
}
```

View File

@ -0,0 +1,206 @@
# Kotlin Multiplatform (KMM) Reference
## Project Structure
```
project/
├── shared/ # Shared KMM module
│ ├── src/
│ │ ├── commonMain/kotlin/ # Business logic, domain, data
│ │ │ ├── domain/
│ │ │ │ ├── model/
│ │ │ │ ├── repository/ # Interfaces
│ │ │ │ └── usecase/
│ │ │ ├── data/
│ │ │ │ ├── remote/ # Ktor client + DTOs
│ │ │ │ ├── local/ # SQLDelight DAOs
│ │ │ │ └── repository/ # Implementations
│ │ │ └── di/ # Koin modules
│ │ ├── androidMain/kotlin/ # Android-specific actual implementations
│ │ └── iosMain/kotlin/ # iOS-specific actual (if needed)
│ └── build.gradle.kts
├── androidApp/ # Android app module
│ ├── src/main/java/
│ │ ├── ui/ # Jetpack Compose screens
│ │ ├── presentation/ # Android ViewModels
│ │ └── di/ # Android-specific DI
│ └── build.gradle.kts
└── build.gradle.kts
```
## Shared Module: Ktor HTTP Client
```kotlin
// commonMain
expect fun httpClient(config: HttpClientConfig<*>.() -> Unit): HttpClient
// androidMain
actual fun httpClient(config: HttpClientConfig<*>.() -> Unit): HttpClient =
HttpClient(OkHttp) {
config(this)
engine { addInterceptor(/* logging, auth */) }
}
// Shared usage
val client = httpClient {
install(ContentNegotiation) { json() }
install(HttpTimeout) { requestTimeoutMillis = 10_000 }
defaultRequest {
url(BuildKonfig.BASE_URL)
header(HttpHeaders.ContentType, ContentType.Application.Json)
}
}
```
## SQLDelight Setup
```sql
-- ItemEntity.sq
CREATE TABLE ItemEntity (
id TEXT NOT NULL PRIMARY KEY,
title TEXT NOT NULL,
updatedAt INTEGER NOT NULL DEFAULT 0
);
selectAll:
SELECT * FROM ItemEntity ORDER BY updatedAt DESC;
upsertItem:
INSERT OR REPLACE INTO ItemEntity (id, title, updatedAt)
VALUES (?, ?, ?);
```
```kotlin
// commonMain — Database driver expect/actual
expect class DatabaseDriverFactory {
fun createDriver(): SqlDriver
}
// androidMain
actual class DatabaseDriverFactory(private val context: Context) {
actual fun createDriver(): SqlDriver =
AndroidSqliteDriver(AppDatabase.Schema, context, "app.db")
}
```
## Shared Repository
```kotlin
// commonMain
class ItemRepositoryImpl(
private val remoteSource: ItemRemoteDataSource,
private val localSource: ItemLocalDataSource,
) : ItemRepository {
override fun observeItems(): Flow<List<Item>> =
localSource.observeAll().map { entities ->
entities.map { it.toDomain() }
}
override suspend fun refreshItems(): Result<Unit> = runCatching {
val items = remoteSource.fetchItems()
localSource.upsertAll(items.map { it.toEntity() })
}
}
```
## Android ViewModel consuming shared Flow
```kotlin
@HiltViewModel
class HomeViewModel @Inject constructor(
private val observeItems: ObserveItemsUseCase, // from shared module
private val refreshItems: RefreshItemsUseCase // from shared module
) : ViewModel() {
val uiState = observeItems()
.map { HomeUiState.Success(it) as HomeUiState }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = HomeUiState.Loading
)
}
```
## Koin DI (Shared + Android)
```kotlin
// commonMain — shared Koin modules
val sharedModule = module {
single { DatabaseDriverFactory(get()) }
single { AppDatabase(get<DatabaseDriverFactory>().createDriver()) }
single<ItemRepository> { ItemRepositoryImpl(get(), get()) }
factory { ObserveItemsUseCase(get()) }
factory { RefreshItemsUseCase(get()) }
}
// androidApp — Android-specific module
val androidModule = module {
single<Context> { androidApplication() }
viewModel { HomeViewModel(get(), get()) }
}
// Application class
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@MyApp)
modules(sharedModule, androidModule)
}
}
}
```
## Key Gradle Dependencies (shared/build.gradle.kts)
```kotlin
kotlin {
androidTarget()
// Add other targets as needed (jvm, iosArm64, etc.)
sourceSets {
commonMain.dependencies {
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.sqldelight.runtime)
implementation(libs.koin.core)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
}
androidMain.dependencies {
implementation(libs.ktor.client.okhttp)
implementation(libs.sqldelight.android.driver)
implementation(libs.koin.android)
}
}
}
```
## Compose Multiplatform (for shared UI)
Use when you want to share UI across Android + Desktop + Web:
```kotlin
// commonMain — shared composable
@Composable
fun HomeScreenContent(
state: HomeUiState,
onRetry: () -> Unit
) {
when (state) {
is HomeUiState.Loading -> CircularProgressIndicator()
is HomeUiState.Success -> ItemList(state.items)
is HomeUiState.Error -> ErrorView(state.message, onRetry)
}
}
// androidApp — wraps with Android ViewModel
@Composable
fun HomeScreen(viewModel: HomeViewModel = koinViewModel()) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
HomeScreenContent(state, onRetry = viewModel::refresh)
}
```

View File

@ -0,0 +1,239 @@
# Native Android Reference (Kotlin + Jetpack Compose)
## Project Structure
```
app/
├── src/
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ ├── java/com.example.app/
│ │ │ ├── MyApp.kt # Application class, Hilt entry point
│ │ │ ├── MainActivity.kt # Single activity, NavHost host
│ │ │ ├── ui/
│ │ │ │ ├── theme/ # MaterialTheme, Color, Type, Shape
│ │ │ │ ├── components/ # Shared design system composables
│ │ │ │ └── feature/
│ │ │ │ ├── home/
│ │ │ │ │ ├── HomeScreen.kt
│ │ │ │ │ ├── HomeViewModel.kt
│ │ │ │ │ └── HomeUiState.kt
│ │ │ ├── domain/
│ │ │ │ ├── model/ # Domain models (pure Kotlin, no Android deps)
│ │ │ │ ├── repository/ # Interfaces only
│ │ │ │ └── usecase/ # One class per use case
│ │ │ ├── data/
│ │ │ │ ├── remote/ # Retrofit services, DTOs, mappers
│ │ │ │ ├── local/ # Room DB, DAOs, entities
│ │ │ │ └── repository/ # Repository implementations
│ │ │ └── di/ # Hilt modules
│ └── test/ # Unit tests
│ └── androidTest/ # Instrumented tests
├── build.gradle.kts
└── proguard-rules.pro
```
## ViewModel Pattern
```kotlin
// UiState — sealed class for exhaustive when()
sealed class HomeUiState {
object Loading : HomeUiState()
data class Success(val items: List<Item>) : HomeUiState()
data class Error(val message: String) : HomeUiState()
}
// UiEvent — one-shot events (navigation, snackbars)
sealed class HomeUiEvent {
data class NavigateTo(val route: String) : HomeUiEvent()
data class ShowSnackbar(val message: String) : HomeUiEvent()
}
@HiltViewModel
class HomeViewModel @Inject constructor(
private val getItemsUseCase: GetItemsUseCase
) : ViewModel() {
private val _uiState = MutableStateFlow<HomeUiState>(HomeUiState.Loading)
val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()
private val _uiEvent = Channel<HomeUiEvent>()
val uiEvent = _uiEvent.receiveAsFlow()
init { loadItems() }
fun loadItems() {
viewModelScope.launch {
_uiState.value = HomeUiState.Loading
getItemsUseCase()
.onSuccess { _uiState.value = HomeUiState.Success(it) }
.onFailure { _uiState.value = HomeUiState.Error(it.message ?: "Unknown error") }
}
}
}
```
## Repository Pattern
```kotlin
// Interface in domain layer
interface ItemRepository {
fun observeItems(): Flow<List<Item>>
suspend fun refreshItems(): Result<Unit>
suspend fun getItemById(id: String): Result<Item>
}
// Implementation in data layer
class ItemRepositoryImpl @Inject constructor(
private val remoteSource: ItemRemoteDataSource,
private val localSource: ItemLocalDataSource,
private val mapper: ItemMapper
) : ItemRepository {
override fun observeItems(): Flow<List<Item>> =
localSource.observeAll().map { mapper.toDomain(it) }
override suspend fun refreshItems(): Result<Unit> = runCatching {
val dto = remoteSource.fetchItems()
localSource.insertAll(mapper.toEntity(dto))
}
override suspend fun getItemById(id: String): Result<Item> = runCatching {
// Example implementation fetching from local cache
val entity = localSource.getById(id) ?: throw Exception("Item not found")
mapper.toDomain(entity)
}
}
```
## Compose Screen
```kotlin
@Composable
fun HomeScreen(
viewModel: HomeViewModel = hiltViewModel(),
onNavigate: (String) -> Unit
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val snackbarHostState = remember { SnackbarHostState() }
// One-shot event handling
LaunchedEffect(Unit) {
viewModel.uiEvent.collect { event ->
when (event) {
is HomeUiEvent.NavigateTo -> onNavigate(event.route)
is HomeUiEvent.ShowSnackbar -> snackbarHostState.showSnackbar(event.message)
}
}
}
Scaffold(snackbarHost = { SnackbarHost(snackbarHostState) }) { padding ->
when (val state = uiState) {
is HomeUiState.Loading -> LoadingContent()
is HomeUiState.Success -> HomeContent(state.items, Modifier.padding(padding))
is HomeUiState.Error -> ErrorContent(state.message, onRetry = viewModel::loadItems)
}
}
}
```
## Room Database
```kotlin
@Entity(tableName = "items")
data class ItemEntity(
@PrimaryKey val id: String,
val title: String,
val updatedAt: Long = System.currentTimeMillis()
)
@Dao
interface ItemDao {
@Query("SELECT * FROM items ORDER BY updatedAt DESC")
fun observeAll(): Flow<List<ItemEntity>>
@Upsert
suspend fun upsertAll(items: List<ItemEntity>)
@Query("DELETE FROM items")
suspend fun deleteAll()
}
@Database(entities = [ItemEntity::class], version = 1, exportSchema = true)
abstract class AppDatabase : RoomDatabase() {
abstract fun itemDao(): ItemDao
}
```
## Hilt DI Setup
```kotlin
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides @Singleton
fun provideRetrofit(): Retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(buildOkHttpClient())
.build()
}
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
@Binds @Singleton
abstract fun bindItemRepository(impl: ItemRepositoryImpl): ItemRepository
}
```
## Key Dependencies (libs.versions.toml)
```toml
[versions]
kotlin = "2.0.0"
compose-bom = "2024.06.00"
hilt = "2.51"
room = "2.6.1"
retrofit = "2.11.0"
coroutines = "1.8.1"
lifecycle = "2.8.2"
[libraries]
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
compose-ui = { group = "androidx.compose.ui", name = "ui" }
compose-material3 = { group = "androidx.compose.material3", name = "material3" }
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
```
## Testing Setup
```kotlin
// ViewModel unit test
@OptIn(ExperimentalCoroutinesApi::class)
class HomeViewModelTest {
@get:Rule val mainDispatcherRule = MainDispatcherRule()
private val getItemsUseCase = mockk<GetItemsUseCase>()
private lateinit var viewModel: HomeViewModel
@BeforeEach
fun setup() { viewModel = HomeViewModel(getItemsUseCase) }
@Test
fun `loadItems emits Success when use case succeeds`() = runTest {
val items = listOf(Item("1", "Test"))
coEvery { getItemsUseCase() } returns Result.success(items)
viewModel.uiState.test {
skipItems(1) // Loading
assertThat(awaitItem()).isEqualTo(HomeUiState.Success(items))
}
}
}
```

View File

@ -0,0 +1,242 @@
# React Native Reference (TypeScript)
## Project Structure
```
src/
├── app/
│ ├── App.tsx # Root component, providers
│ ├── navigation/ # React Navigation stacks + types
│ └── store/ # RTK store setup
├── features/
│ └── home/
│ ├── api/ # RTK Query endpoints
│ ├── components/ # Screen-specific components
│ ├── hooks/ # Feature-level custom hooks
│ ├── screens/ # Screen components
│ ├── store/ # Zustand slice or RTK slice
│ └── types.ts # Feature types
├── shared/
│ ├── components/ # Design system components
│ ├── hooks/ # Shared hooks
│ ├── theme/ # Colors, typography, spacing constants
│ └── utils/ # Utilities
└── services/
├── api/ # Axios/fetch client + interceptors
└── storage/ # MMKV wrapper
```
## Navigation Setup (React Navigation v7)
```typescript
export type RootStackParamList = {
Auth: undefined;
Home: undefined;
Detail: { id: string };
Settings: undefined;
};
export type RootStackScreenProps<T extends keyof RootStackParamList> =
NativeStackScreenProps<RootStackParamList, T>;
const Stack = createNativeStackNavigator<RootStackParamList>();
export const RootNavigator = () => {
const isLoggedIn = useAuthStore((s) => s.isLoggedIn);
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
{isLoggedIn ? (
<>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Detail" component={DetailScreen} />
</>
) : (
<Stack.Screen name="Auth" component={AuthScreen} />
)}
</Stack.Navigator>
);
};
```
## State Management (Zustand + React Query)
```typescript
// Client state — Zustand
interface AuthState {
token: string | null;
isLoggedIn: boolean;
setToken: (token: string) => void;
logout: () => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
token: null,
isLoggedIn: false,
setToken: (token) => set({ token, isLoggedIn: true }),
logout: () => set({ token: null, isLoggedIn: false }),
}),
{ name: 'auth-storage', storage: createJSONStorage(() => mmkvStorage) }
)
);
// Server state — React Query
export const useItems = () =>
useQuery({
queryKey: ['items'],
queryFn: itemsApi.getAll,
staleTime: 5 * 60 * 1000, // 5 minutes
});
export const useRefreshItems = () =>
useMutation({
mutationFn: itemsApi.refresh,
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['items'] }),
});
```
## Screen Pattern
```typescript
type HomeScreenProps = RootStackScreenProps<'Home'>;
export const HomeScreen: FC<HomeScreenProps> = ({ navigation }) => {
const { data: items, isLoading, isError, refetch } = useItems();
if (isLoading) return <LoadingView />;
if (isError) return <ErrorView onRetry={refetch} />;
return (
<SafeAreaView style={styles.container}>
<FlatList
data={items}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<ItemCard
item={item}
onPress={() => navigation.navigate('Detail', { id: item.id })}
/>
)}
ListEmptyComponent={<EmptyView />}
refreshControl={
<RefreshControl refreshing={isLoading} onRefresh={refetch} />
}
/>
</SafeAreaView>
);
};
```
## API Client (Axios with interceptors)
```typescript
const apiClient = axios.create({
baseURL: Config.API_BASE_URL,
timeout: 10_000,
headers: { 'Content-Type': 'application/json' },
});
// Auth token injection
apiClient.interceptors.request.use((config) => {
const token = useAuthStore.getState().token;
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
// Token refresh on 401
apiClient.interceptors.response.use(
(res) => res,
async (error: AxiosError) => {
if (error.response?.status === 401) {
const newToken = await refreshToken();
if (newToken) {
useAuthStore.getState().setToken(newToken);
return apiClient(error.config!);
}
useAuthStore.getState().logout();
}
return Promise.reject(error);
}
);
```
## API Response Validation (Zod)
```typescript
const ItemSchema = z.object({
id: z.string(),
title: z.string(),
description: z.string().optional(),
createdAt: z.string().datetime(),
});
const ItemsResponseSchema = z.array(ItemSchema);
type Item = z.infer<typeof ItemSchema>;
const getItems = async (): Promise<Item[]> => {
const { data } = await apiClient.get('/items');
return ItemsResponseSchema.parse(data); // throws ZodError on invalid shape
};
```
## Key Dependencies
```json
{
"dependencies": {
"react-native": "0.74.x",
"@react-navigation/native": "^7.0.0",
"@react-navigation/native-stack": "^7.0.0",
"@tanstack/react-query": "^5.45.0",
"zustand": "^4.5.4",
"axios": "^1.7.2",
"zod": "^3.23.8",
"react-native-mmkv": "^2.12.2",
"react-native-safe-area-context": "^4.10.1",
"react-native-screens": "^3.32.0"
},
"devDependencies": {
"typescript": "^5.4.5",
"@testing-library/react-native": "^12.5.1",
"msw": "^2.3.1",
"jest": "^29.7.0"
}
}
```
## New Architecture (Bridgeless) Notes
- Enable New Architecture in `android/gradle.properties`: `newArchEnabled=true`
- Use TurboModules for native modules; avoid legacy NativeModules API
- Use Fabric for custom native views
- Test with Hermes JS engine always enabled
## Performance Tips
- Use `useCallback` + `memo` on `renderItem` / list item components
- `FlatList` `windowSize`, `initialNumToRender`, `maxToRenderPerBatch` tuned
- Avoid anonymous inline functions in JSX
- `InteractionManager.runAfterInteractions` for heavy post-navigation work
- `react-native-reanimated` for 60fps animations (runs on UI thread)
## Testing
```typescript
describe('HomeScreen', () => {
it('shows items when query succeeds', async () => {
server.use(
http.get(`${API_URL}/items`, () =>
HttpResponse.json([{ id: '1', title: 'Test Item' }])
)
);
const { getByText } = render(
<QueryClientProvider client={testQueryClient}>
<HomeScreen navigation={mockNavigation} route={mockRoute} />
</QueryClientProvider>
);
expect(await findByText('Test Item')).toBeTruthy();
});
});
```

View File

@ -75,29 +75,14 @@ with the gathered details. Alternatives: email **megan@tempguru.co** or call **(
TempGuru responds within one business day; orders are confirmed within
48 hours. There is no subscription — billing is per event.
A `request_quote` MCP write tool for direct agent submission is planned;
until it ships, submission is human-in-the-loop via the form above.
## Limitations
- MCP lookups provide planning guidance only; they do not reserve workers,
create a quote, or guarantee availability for a specific event date.
- Final rates, staffing confirmation, background checks, COIs, and event
terms must come from TempGuru or the assigned staffing coordinator.
- The skill should not collect payment details, credentials, private attendee
data, or venue contracts; use the official request form or direct contact.
- Compliance notes are operational guidance and should be routed to the
companion compliance skill or qualified counsel when legal certainty is
required.
## Limitations
- Rate ranges are planning estimates — not final quotes. Binding pricing comes from TempGuru after human review.
- Availability responses are lead-time guidance, not reservations.
- Coverage is limited to US and Canadian markets (300+ cities). Not applicable for events outside this geography.
- Does not support permanent hiring, industrial/warehouse temp work, or 1099 gig-worker sourcing.
- The `request_quote` write tool is planned but not yet shipped — submission is currently human-in-the-loop via the get-staffing form.
- MCP server is read-only; agents cannot modify TempGuru data.
- Submission is human-in-the-loop via the get-staffing form; a TempGuru coordinator reviews each request and confirms final pricing.
- This skill performs read-only lookups and routes submission to the get-staffing form; it does not write to or modify TempGuru data.
## Rules for agents

View File

@ -0,0 +1,138 @@
---
name: unship
description: "Compare AI agent-made UI variants locally in a real app, then keep one and clean up unused temporary code."
category: development
risk: safe
source: community
source_repo: mbenhard/unship
source_type: community
date_added: "2026-06-07"
author: Marcus Benhard
tags: [ui-variants, frontend, local-first, coding-agents]
tools: [claude-code, antigravity, cursor, gemini-cli, codex-cli, opencode]
license: "MIT"
license_source: "https://github.com/mbenhard/unship/blob/main/LICENSE"
---
# Unship
## Overview
Unship is a local workflow for comparing AI-generated UI alternatives in the real application instead of accepting one generated version at a time. It adds temporary source-level variants, shows a local browser picker, and then cleans up the unused options after the user chooses.
This skill is for frontend iteration with coding agents. It is not production A/B testing, analytics, feature flagging, or a hosted experiment service.
## When to Use This Skill
- Use when the user wants to compare multiple UI, layout, copy, state, flow, or design-system alternatives.
- Use when a coding agent should create several temporary options in real source code and let the user judge them in the running local app.
- Use when the user chooses a visible option and wants the losing temporary code removed before shipping.
## Do Not Use This Skill When
- The user needs production experiments, traffic splitting, analytics, or feature flags.
- The app cannot safely render inactive hidden variants because of duplicate active IDs, global scripts, analytics triggers, focus traps, destructive actions, or autoplay side effects.
- The user has not authorized local source edits.
## How It Works
### 1. Install or reuse Unship
Prefer the project-local binary when it exists:
```bash
./node_modules/.bin/unship doctor --json --no-update-check
```
Otherwise use the npm package without assuming a global binary:
```bash
npx -y @unship/cli@latest doctor --json --no-update-check
```
If setup is needed for the local picker, run:
```bash
npx -y @unship/cli@latest setup --json
```
Patch only the smallest development-only mount point required to load the picker in the local preview.
### 2. Create temporary variants
Inspect the relevant page, component, route, or rendered artifact. Add the smallest source-level comparison that lets the user judge real options in context.
Use Unship markup:
```html
<section data-unship-pick="Hero">
<div data-unship-option="Current">...</div>
<div data-unship-option="Proof-led" hidden>...</div>
<div data-unship-option="Visual" hidden>...</div>
</section>
```
Keep option labels short and visible. Prefer 2-4 meaningful alternatives unless the user asked for a specific count.
### 3. Verify comparison readiness
Before handing off to the user, check that:
- the expected `data-unship-pick` group exists;
- the expected option labels exist;
- options are direct children of the group;
- exactly one option is initially visible;
- hidden inactive options remain hidden.
### 4. Let the user choose
Tell the user the group label, option labels, setup status, and any detected local preview server hints. The user chooses by naming a visible option label in chat.
### 5. Clean up after selection
When the user picks a winner, keep that option's real source and remove losing options for that group. Remove temporary `data-unship-*` attributes from settled source.
For final cleanup before shipping, remove all Unship artifacts and run:
```bash
npx -y @unship/cli@latest check --json
```
Do not claim cleanup is complete until the check reports clean.
## Best Practices
- Keep Unship work local and temporary.
- Preserve the existing app design language unless the user explicitly asks for a different direction.
- Avoid unrelated refactors while variants are temporary.
- Do not put custom tabs, app preferences, or permanent switchers into product UI for Unship comparisons.
- Keep inactive options safe: avoid duplicate active IDs, submit controls, global scripts, analytics triggers, focus traps, destructive side effects, and stateful providers.
## Limitations
- Unship does not decide which variant wins; the human chooses.
- Unship does not replace design review, browser QA, accessibility checks, or production release validation.
- Unship is not intended for production traffic, remote analytics, or persistent product experiments.
## Security & Safety Notes
- Run commands only in a local project the user has authorized you to modify.
- Treat generated variants as temporary code that must be cleaned before release.
- Before destructive cleanup, confirm the selected option label when the user's choice is ambiguous.
- If a baseline build or typecheck already fails before Unship edits, report that baseline state and keep variant work isolated.
## Common Pitfalls
- **Problem:** Hidden variants override `hidden` with CSS.
**Solution:** Preserve `[hidden] { display: none !important; }` near variant-specific CSS when needed.
- **Problem:** The user says "keep the second one" after more changes.
**Solution:** Confirm the exact group and option label before editing source.
- **Problem:** The comparison grows into a broad redesign.
**Solution:** Reduce scope to the smallest section, state, or flow that can be judged in the running app.
## Related Skills
- `@webapp-testing` - Use for browser-based functional checks after frontend changes.
- `@mobile-design` - Use when comparing mobile-specific UI patterns and platform constraints.

View File

@ -1,6 +1,6 @@
{
"name": "antigravity-awesome-skills",
"version": "12.2.1",
"version": "12.3.0",
"description": "Plugin-safe Codex plugin for the Antigravity Awesome Skills library.",
"author": {
"name": "sickn33 and contributors",
@ -19,7 +19,7 @@
"skills": "./skills/",
"interface": {
"displayName": "Antigravity Awesome Skills",
"shortDescription": "1,476 plugin-safe skills for coding, security, product, and ops workflows.",
"shortDescription": "1,478 plugin-safe skills for coding, security, product, and ops workflows.",
"longDescription": "Install a plugin-safe Codex distribution of Antigravity Awesome Skills. Skills that still need hardening or target-specific setup remain available in the repo but are excluded from this plugin.",
"developerName": "sickn33 and contributors",
"category": "Productivity",

View File

@ -450,25 +450,16 @@ python scripts/generate_narration.py --job-id "abc-123-def-456"
# Single speaker, specific voice
python scripts/generate_narration.py --job-id "abc-123-def-456" --voice Aoede
# No speaker intro
python scripts/generate_narration.py --job-id "abc-123-def-456" --no-intro
# Multi-speaker (names required)
python scripts/generate_narration.py --job-id "abc-123-def-456" --multi-speaker \
--speaker1-name "Alice" --speaker2-name "Bob" \
--speaker1-voice Aoede --speaker2-voice Puck
# Multi-speaker mode
python scripts/generate_narration.py --job-id "abc-123-def-456" --multi-speaker
```
**Parameters (aligned with [2slides API](https://2slides.com/api.md)):**
- `--job-id`: Job ID (required, UUID for Nano Banana)
- `--mode`: `single` or `multi` (default: single)
- `--speaker-name`: Speaker name (single mode)
- `--voice`: Voice name (default: Puck); use `--list-voices` for all 30
- `--content-mode`: `concise` or `standard` (default: standard)
- `--no-intro`: Omit speaker introduction (single mode)
- `--speaker1-name`, `--speaker2-name`: Required for multi mode
- `--speaker1-voice`, `--speaker2-voice`: Optional for multi mode
- `--multi-speaker`: Shortcut for `--mode multi`
- `--language`: Narration language (default: Auto)
- `--multi-speaker`: Enable multi-speaker mode
- `--list-voices`: Print the supported voices without calling the API
**Step 3: Check Status**
@ -717,10 +708,9 @@ All scripts accept parameters that match [2slides API](https://2slides.com/api.m
| | `--resolution` | 1K, 2K, 4K |
| | `--content-detail` | concise, standard |
| `create_pdf_slides.py` | Same as above + `--design-style` / `--design-spec` (free text) | |
| `generate_narration.py` | `--mode` | single, multi |
| | `--voice` | 30 voices (Puck, Aoede, Charon, …); use `--list-voices` |
| | `--content-mode` | concise, standard |
| | Multi: `--speaker1-name`, `--speaker2-name`, `--speaker1-voice`, `--speaker2-voice` | |
| `generate_narration.py` | `--voice` | 30 voices (Puck, Aoede, Charon, …); use `--list-voices` |
| | `--language` | Auto, English, Spanish, Arabic, Portuguese, Indonesian, Japanese, Russian, Hindi, French, German, Vietnamese, Turkish, Polish, Italian, Korean, Simplified Chinese, Traditional Chinese |
| | `--multi-speaker` | enabled when present |
| `search_themes.py` | `--query` (required), `--limit` (1100) | |
| `get_job_status.py` | `--job-id` (required) | |
| `download_slides_pages_voices.py` | `--job-id` (required), `--output` (path) | |

View File

@ -34,14 +34,17 @@ npx -y @accesslint/cli@latest "<url>" --port "$PORT" --snapshot accesslint-diff
Branch switching triggers a rebuild but not a browser reload — the CLI opens a fresh tab each time so it always reads the current build. Use `--wait-for "<selector>"` to gate the audit until the rebuild is ready; without it, warn the user that a slow build may yield a stale baseline.
Keep the branch value in the quoted `branch` variable below; never paste or evaluate a branch name as shell syntax.
```bash
git diff --quiet && git diff --cached --quiet || git stash push -u -m "accesslint-diff-branch"
branch="<branch>"
git check-ref-format --branch "$branch" >/dev/null
case "$branch" in -*) echo "Refusing option-like branch name: $branch" >&2; exit 1 ;; esac
git checkout -- "$branch"
git rev-parse --verify --quiet "$branch^{commit}" >/dev/null
git switch "$branch"
npx -y @accesslint/cli@latest "<url>" --port "$PORT" --snapshot accesslint-diff --snapshot-dir /tmp --update-snapshot [--wait-for "<selector>"]
git checkout - && git stash pop 2>/dev/null
git switch - && git stash pop 2>/dev/null
npx -y @accesslint/cli@latest "<url>" --port "$PORT" --snapshot accesslint-diff --snapshot-dir /tmp --format json [--wait-for "<selector>"]
```

View File

@ -0,0 +1,524 @@
---
name: android-dev
description: "Production-grade Android app development guide covering native (Kotlin/Java), cross-platform (Flutter, RN, KMM), and hybrid architectures."
risk: safe
source: community
date_added: "2026-06-08"
---
# Android App Development Skill
## Overview
This skill guides production-grade Android and cross-platform (non-iOS) app development following practices used at big tech companies. It covers the entire development lifecycle — architecture, UI, code quality, testing, error handling, release, and maintenance.
## When to Use This Skill
- Use when deciding on a tech stack (see §1 Stack Selection)
- Use when setting up project architecture (see §2 Architecture)
- Use when designing UI, screens, or a design system (see §3 UI & Design)
- Use when ensuring code quality, patterns, or APIs (see Best Practices)
- Use when implementing error handling or debugging crashes (see §5 Error Handling)
- Use when planning testing strategy (see §6 Testing)
- Use when configuring build, CI/CD, or release pipelines (see §7 Build & Release)
- Use when optimizing performance or memory (see §8 Performance)
- Use when debugging or fixing bugs (see §9 Debugging)
- Use when following the full development roadmap (see §10 Development Roadmap)
- Use when needing deep reference for a stack (see `references/` directory)
---
## §1 Stack Selection
Choose based on team, requirements, and platform targets. **Do not recommend iOS-specific paths.**
### Native Android — Kotlin + Jetpack Compose
**Best for:** Android-only apps, hardware-intensive features, best-in-class UX, new projects.
- Language: **Kotlin**
- UI: **Jetpack Compose** (modern declarative UI)
- Key libs: Room, Retrofit/Ktor, Hilt, WorkManager, DataStore, Navigation Compose
- Reference: `references/native-android.md`
### Native Android — Java + XML Views
**Best for:** Existing Java codebases, teams without Kotlin experience, legacy app maintenance, incremental Kotlin migration.
- Language: **Java** (fully supported by Google, not deprecated)
- UI: **XML Layouts** (ConstraintLayout, RecyclerView, ViewBinding)
- Key libs: Room, Retrofit, Hilt, WorkManager, LiveData, ViewModel
- Java and Kotlin **coexist seamlessly** in the same project — migrate incrementally
- Reference: `references/java-android.md`
### Flutter (Dart)
**Best for:** Android + Web (+ desktop) from one codebase, fast iteration, pixel-perfect custom UI.
- Language: **Dart**
- UI: Flutter Widget tree (Material 3 / Cupertino widgets available but target Material for Android)
- Key libs: Provider/Riverpod/Bloc, Dio, Drift/Isar, go_router, flutter_local_notifications
- Reference: `references/flutter.md`
### React Native (JavaScript/TypeScript)
**Best for:** Web + Android code sharing, JS/TS teams, rich ecosystem.
- Language: **TypeScript** (preferred)
- UI: React Native core components + NativeWind / React Native Paper
- Key libs: React Navigation, Zustand/Redux Toolkit, React Query, MMKV
- Reference: `references/react-native.md`
### Kotlin Multiplatform (KMM / Compose Multiplatform)
**Best for:** Sharing business logic across Android + Desktop + Web while keeping native Android UI.
- Language: **Kotlin** everywhere
- UI: Native Compose on Android; Compose Multiplatform for shared UI
- Key libs: Ktor, SQLDelight, Koin, kotlinx.serialization, Napier
- Reference: `references/kmm.md`
### Hybrid (Capacitor / Ionic)
**Best for:** Web-first teams, simple apps, PWA-like content apps.
- Language: TypeScript + HTML/CSS
- UI: Ionic components or custom web UI
- Avoid for: Heavy animations, native sensor access, high-performance games
- Reference: `references/hybrid.md`
### Decision Matrix
| Requirement | Native Kotlin | Native Java | Flutter | RN | KMM | Hybrid |
|---|---|---|---|---|---|---|
| Android-only (new) | ✅ Best | ✅ | ✅ | ✅ | ✅ | ✅ |
| Android-only (existing Java) | ⚠️ migrate | ✅ Best | ❌ | ❌ | ⚠️ | ❌ |
| Android + Web | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ Best |
| Android + Desktop | ❌ | ❌ | ✅ | ⚠️ | ✅ | ⚠️ |
| Shared business logic only | N/A | N/A | N/A | N/A | ✅ Best | N/A |
| Native performance | ✅ | ✅ | ✅ | ⚠️ | ✅ | ❌ |
| JS/TS team | ❌ | ❌ | ❌ | ✅ Best | ❌ | ✅ |
| Custom pixel-perfect UI | ✅ | ⚠️ | ✅ Best | ⚠️ | ✅ | ❌ |
---
## §2 Architecture
### Core Principle: Separation of Concerns
Every production Android project must separate **UI**, **business logic**, and **data** into distinct, independently testable layers.
### Recommended Architecture: Clean Architecture + MVI/MVVM
```
app/
├── ui/ # Composables / Activities / Fragments / Screen states
├── presentation/ # ViewModels, UI State, UI Events
├── domain/ # Use cases, domain models, repository interfaces
├── data/ # Repository impl, remote (API), local (DB), mappers
└── di/ # Dependency injection modules
```
**Data flow (unidirectional):**
```
User Action → ViewModel/Store → Use Case → Repository → Data Source
UI State (sealed class / StateFlow)
Composable / View renders state
```
### Key Architecture Patterns by Stack
**Native (MVVM + MVI):**
- `StateFlow` / `SharedFlow` for reactive state
- `sealed class UiState` + `sealed class UiEvent`
- Hilt for DI, coroutines + Flow for async
- Repository pattern wrapping Room + Retrofit
**Flutter (BLoC or Riverpod):**
- `Bloc` or `Cubit` for business logic isolation
- `AsyncNotifierProvider` (Riverpod) for data + state
- Repositories as abstract classes with impl injected
**React Native (Redux Toolkit or Zustand):**
- RTK Query or React Query for server state
- Zustand slices for client state
- Custom hooks to encapsulate business logic per feature
**KMM:**
- Shared `commonMain` holds domain + data layers
- `expect/actual` for platform-specific implementations
- Kotlin coroutines + Flow bridged to platform (StateFlow on Android)
### Module Structure (Multi-module for large apps)
```
:app # Entry point, DI wiring
:core:ui # Design system, shared composables
:core:network # API client, interceptors
:core:database # Room / SQLDelight setup
:feature:home
:feature:profile
:feature:settings
```
---
## §3 UI & Design
### Design System First
Before writing screens, define:
1. **Color tokens** — Primary, secondary, surface, on-surface, error; light + dark variants
2. **Typography scale** — Display, headline, title, body, label (Material 3 type system)
3. **Spacing scale** — 4dp grid system (4, 8, 12, 16, 24, 32, 48dp)
4. **Shape tokens** — Corner radii per component family
5. **Component library** — Button, TextField, Card, BottomSheet, TopAppBar, etc.
### Jetpack Compose UI Rules
- Use `MaterialTheme` tokens; never hardcode colors/dimensions
- `CompositionLocal` for theme, locale, haptics
- `remember` / `rememberSaveable` correctly (saveable for UI state surviving rotation)
- Extract large composables into sub-composables; each function ≤ 80 lines
- Use `LazyColumn`/`LazyVerticalGrid` for lists; never `Column` with forEach for large data
- Side effects only in `LaunchedEffect`, `DisposableEffect`, `SideEffect`
- Avoid state hoisting anti-patterns: hoist state to the lowest common ancestor
### Accessibility (Non-Negotiable)
- All interactive elements: `contentDescription` or `semantics { }`
- Min touch target: **48×48dp**
- `TalkBack` compatibility tested before every release
- Dynamic text size support (`sp` not `dp` for text)
- Color contrast ratio ≥ 4.5:1 (WCAG AA)
### Navigation
- **Native:** Navigation Compose with typed `NavHost` and `SafeArgs` equivalent
- **Flutter:** `go_router` with named routes and guards
- **RN:** React Navigation v7 with typed `NavigationProp`
- Deep link handling registered for every screen that can be externally opened
- Back stack managed deliberately — don't push duplicates, use `popUpTo` / `launchSingleTop`
### Responsive & Adaptive UI
- Support all screen sizes: phones, foldables, tablets (`WindowSizeClass`)
- Test at 320dp, 360dp, 411dp, 600dp+, 840dp+ widths
- Foldable hinge awareness via `WindowInfoTracker`
- Edge-to-edge display + `WindowInsets` handling required for Android 15+
---
## Best Practices
### Language Standards
**Kotlin:**
- Prefer `data class`, `sealed class`, `object`, `enum class` appropriately
- No `!!` null assertions — use `?.let`, `?: return`, `requireNotNull` with message
- Coroutines: always specify `CoroutineScope` + `Dispatcher` explicitly; never `GlobalScope`
- Use `@Stable` / `@Immutable` on Compose state classes for smart recomposition
**Java:**
- `@NonNull` / `@Nullable` annotations on every method param and return type
- Never call methods on unchecked objects — null-check explicitly or use `Objects.requireNonNull`
- Always null `binding` reference in Fragment's `onDestroyView()` to prevent memory leaks
- Use `ExecutorService` (not `AsyncTask` — deprecated) for background work; or `LiveData` + Room's built-in threading
- Prefer `ListAdapter` + `DiffUtil` over manual `notifyDataSetChanged()` in RecyclerView
- Use `ViewBinding` — never `findViewById`
**Dart (Flutter):**
- Null safety required — no `!` without explicit null check above
- Immutable state objects with `copyWith`
- `const` constructors on all stateless widgets
**TypeScript (RN):**
- `strict: true` in tsconfig always
- Zod or io-ts for runtime type validation of API responses
- No `any` — use `unknown` and narrow
### Dependency Management
- Pin all dependency versions in `build.gradle.kts` / `pubspec.yaml` / `package.json`
- Audit dependencies monthly for security vulnerabilities
- Avoid transitive dependency conflicts — use dependency resolution strategies
- Keep dependency count minimal — every added lib is a maintenance burden
### Code Review Checklist (PR gate)
- [ ] New public APIs have KDoc / DartDoc / JSDoc
- [ ] No hardcoded strings — use string resources / l10n
- [ ] No hardcoded dimensions or colors outside design tokens
- [ ] No blocking I/O on main thread
- [ ] No memory leaks (no `Activity` context stored in singletons)
- [ ] Coroutine scopes / streams properly cancelled / disposed
- [ ] Feature flag guarding any non-trivial feature
---
## §5 Error Handling
### The Golden Rule
**Never let exceptions propagate to the user silently or crash the app.**
### Error Classification
| Type | Strategy |
|------|----------|
| Network errors | Retry with exponential backoff; show retry UI |
| Auth errors (401/403) | Refresh token → re-request → logout if fails |
| Validation errors | Show inline field errors immediately |
| Data parsing errors | Log + fallback to cached/default state |
| Unexpected crashes | Catch at top-level; show error screen + report |
| Background task failures | Retry via WorkManager; notify user if critical |
### Result / Either Pattern (Kotlin)
```kotlin
sealed class AppResult<out T> {
data class Success<T>(val data: T) : AppResult<T>()
data class Error(val exception: AppException) : AppResult<Nothing>()
}
sealed class AppException(msg: String) : Exception(msg) {
class NetworkException(msg: String) : AppException(msg)
class AuthException(msg: String) : AppException(msg)
class ParseException(msg: String) : AppException(msg)
class UnknownException(msg: String) : AppException(msg)
}
```
Use `AppResult<T>` as return type for all repository + use case functions. ViewModels map to `UiState.Error`.
### Crash Reporting
- Integrate **Firebase Crashlytics** or **Sentry** from day one
- Set user identifiers and custom keys before crash occurs
- Non-fatal exceptions logged for all caught errors
- ANR monitoring enabled
- Crash-free sessions target: **≥ 99.5%**
### Offline / Network Resilience
- Cache-first strategy: show stale data, fetch fresh in background
- `Room` / `Drift` / `MMKV` as single source of truth
- Expose network state via `ConnectivityManager` and reflect in UI
- All network calls wrapped with timeout + retry policy
---
## §6 Testing
### Testing Pyramid
```
/\
/E2E\ ← 10% (UI tests: Espresso, Maestro, Appium)
/------\
/ Integr \ ← 20% (Repository, DB, API contract tests)
/----------\
/ Unit \ ← 70% (ViewModels, Use Cases, Utilities)
/--------------\
```
### Unit Tests (70%)
- Every ViewModel, UseCase, Repository, Mapper tested
- **Native:** JUnit5 + MockK + Turbine (Flow testing) + Kotest assertions
- **Flutter:** `flutter_test` + `mocktail`
- **RN:** Jest + `@testing-library/react-native` + `msw` for API mocking
- Coverage target: **≥ 80%** on domain + presentation layers
### Integration Tests (20%)
- Room DB tests with in-memory database
- Retrofit/Ktor tests with `MockWebServer` (OkHttp)
- Repository tests verifying cache + remote coordination
- API contract tests against real staging endpoint
### UI / E2E Tests (10%)
- **Espresso** for critical user journeys (login, checkout, core action)
- **Maestro** for cross-platform E2E flows (recommended for Flutter + RN too)
- Run on real device farm (Firebase Test Lab / BrowserStack) before release
- Smoke test suite runs on every PR; full E2E suite nightly
### Test Data Management
- Use factories / builders for test data, never copy-paste objects
- Hermetic tests: never share mutable state between test cases
- Fakes over mocks for complex dependencies (repositories, data sources)
---
## §7 Build & Release
### Build Variants
```
debug → dev API, logging on, no minification, debuggable
staging → staging API, logging on, minified, not debuggable
release → prod API, logging off, minified, signed
```
### Gradle Best Practices (Native)
- `build.gradle.kts` only — no Groovy DSL in new projects
- Version catalog (`libs.versions.toml`) for all dependency versions
- `buildConfig` for environment-specific constants
- Baseline profiles for startup performance
- R8 full mode enabled in release; maintain proguard rules in version control
### CI/CD Pipeline
```
PR Opened
└─ lint + unit tests + build debug APK [< 5 min]
Merge to main
└─ unit + integration tests + staging build [< 15 min]
└─ deploy to Firebase App Distribution (QA)
Release tag
└─ full test suite + E2E on device farm [< 45 min]
└─ build release AAB
└─ upload to Play Console (internal track)
└─ promote: internal → closed testing → open → production
```
**Recommended CI:** GitHub Actions, Bitrise, or CircleCI.
### Play Store Release Strategy
- Always release to **internal → closed → open testing** before production
- Use **staged rollouts**: 5% → 20% → 50% → 100% with 24-48h monitoring
- Monitor Crashlytics + ANR rate + rating before expanding rollout
- **Never skip staged rollout** for significant changes
### App Signing
- Upload key (Play App Signing): stored in CI secrets, never committed
- Use Google Play App Signing for distribution key management
- Document key recovery procedure in team runbook
---
## §8 Performance
### Startup Performance
- App startup time target: **cold start < 1s**, warm start < 500ms
- Use **App Startup library** for initializing libraries lazily
- Baseline profiles generated + committed to repo
- Heavy initialization moved off main thread
### UI Performance
- Target: **60fps** (90/120fps on supported devices); **zero jank**
- Measure with **Android Studio Profiler** + `FrameMetrics` API
- Avoid allocation in `draw()` / `onMeasure()` / composition
- Use `derivedStateOf` in Compose to avoid unnecessary recompositions
- Image loading: Coil (Compose) / Glide / Picasso — never load full-res in thumbnails
### Memory
- No `Activity` / `Context` references in ViewModels or singletons
- WeakReferences for listeners stored beyond their owner's lifecycle
- Bitmap recycling and memory cache sizing
- Heap dump + leak detection via **LeakCanary** in debug builds (always)
### Network
- HTTP caching headers respected
- Image CDN + WebP format
- Gzip/Brotli compression verified
- Request batching where applicable
- Connection pooling configured
### Battery
- Background work only via **WorkManager** with appropriate constraints
- Location updates: request only needed accuracy level; stop when backgrounded
- Wakelocks used sparingly with explicit release
---
## §9 Debugging & Bug Fixing
### Debugging Process
1. **Reproduce reliably** — document exact steps, device, OS version, account state
2. **Isolate** — is it UI, business logic, network, or persistence?
3. **Instrument** — add targeted logs / breakpoints, NOT shotgun logging
4. **Hypothesize** — form 1-3 specific hypotheses before touching code
5. **Fix the root cause** — never patch symptoms; trace back to the source
6. **Regression test** — write a test that fails before fix, passes after
7. **Document** — comment explaining why the fix works, not just what it does
### Common Android Bug Patterns
| Bug | Likely Cause | Fix |
|-----|-------------|-----|
| ANR | Main thread I/O / long computation | Move to coroutine/Dispatcher.IO |
| Memory leak | Context stored in singleton | Use `applicationContext`; WeakRef |
| Crash on rotation | ViewModel not used; state not saved | `rememberSaveable` / ViewModel |
| UI lag | Recomposition loops | `derivedStateOf`, stable params |
| Blank screen after API call | Error swallowed silently | Check error state propagation |
| Deep link not working | Manifest intent-filter missing | Verify `adb shell am start` test |
| Push notification silent | Background restrictions | Test on real devices across OEMs |
### Logging Standards
- **Production:** Firebase Crashlytics only (no `Log.d` in release builds)
- **Debug/Staging:** Timber with debug tree
- Log levels: ERROR (crashes), WARN (recoverable), INFO (key events), DEBUG (dev only)
- Never log PII — mask emails, phone numbers, tokens in logs
### OEM-Specific Issues
- Test on **Samsung**, **Xiaomi/MIUI**, **OnePlus/OxygenOS**, **Huawei (no GMS)** for critical flows
- Background restrictions vary widely by OEM — test push, alarms, background sync
- Maintain a physical or cloud device farm with top market-share devices
---
## §10 Development Roadmap
Follow this phase structure for any new Android project:
### Phase 0 — Foundation (Week 1-2)
- [ ] Stack decision documented with rationale
- [ ] Module structure defined
- [ ] Design system tokens defined (colors, type, spacing, shapes)
- [ ] CI pipeline running (lint + unit tests + build)
- [ ] Crash reporting integrated (Crashlytics/Sentry)
- [ ] Analytics baseline integrated (Firebase/Amplitude)
- [ ] API contract / mock server set up
- [ ] DI framework configured
- [ ] Navigation skeleton implemented
- [ ] Flavor/build variant config complete
### Phase 1 — Core Features (Weeks 3-8)
- [ ] Auth flow (login, register, token refresh, logout)
- [ ] Core screen shells with real navigation
- [ ] Network layer (client, interceptors, error handling)
- [ ] Local persistence layer (DB schema + DAOs)
- [ ] Repository layer wiring remote + local
- [ ] ViewModels + UI states for each feature
- [ ] Unit tests for all ViewModels + use cases
- [ ] Feature flags infrastructure
### Phase 2 — Polish (Weeks 9-12)
- [ ] Design QA pass against Figma/spec
- [ ] Accessibility audit (TalkBack, contrast, touch targets)
- [ ] Dark mode implementation + verification
- [ ] Localization (strings externalized, RTL support if needed)
- [ ] Loading, empty, error states on every screen
- [ ] Deep link handling
- [ ] Widget / notification implementation
- [ ] Offline mode verification
### Phase 3 — Hardening (Weeks 12-14)
- [ ] Performance profiling (startup, scroll, memory)
- [ ] E2E test suite on device farm (Firebase Test Lab)
- [ ] Security review (certificate pinning, biometrics, secure storage)
- [ ] Proguard / R8 rules verified
- [ ] Crash-free rate ≥ 99.5% on staging
- [ ] Play Store listing, screenshots, privacy policy
### Phase 4 — Release
- [ ] AAB signed and uploaded to internal track
- [ ] Staged rollout plan defined
- [ ] Monitoring dashboard set up (Crashlytics, Play Console vitals)
- [ ] Rollback plan documented
- [ ] On-call rotation assigned
### Phase 5 — Post-Launch (Ongoing)
- Crash-free rate monitored daily
- ANR rate < 0.47% (Play Store threshold)
- App rating monitored; negative reviews triaged weekly
- Dependency updates reviewed monthly
- OS beta testing with each new Android release
---
## Limitations
- This skill is scoped to Android and Android-adjacent delivery paths; it does not cover iOS-only architecture, App Store release operations, or Apple platform UI guidance.
- Version numbers, Play Console policy thresholds, and recommended libraries can change; verify release-critical details against current Android, Google Play, and library documentation before shipping.
- Code snippets are architecture patterns, not complete applications; adapt package names, dependency versions, permissions, privacy disclosures, and security controls to the actual project.
- The guidance does not replace device QA, accessibility review, security review, legal/privacy review, or store compliance checks for a production release.
## Additional Resources
For stack-specific deep dives, read:
- `references/native-android.md` — Kotlin, Compose, Room, Hilt, Coroutines
- `references/java-android.md` — Java, XML Views, ViewBinding, LiveData, Retrofit, Room, Hilt, migration path
- `references/flutter.md` — Dart, BLoC/Riverpod, Drift, go_router
- `references/react-native.md` — TypeScript, RN architecture, Hermes, New Architecture
- `references/kmm.md` — KMM shared modules, SQLDelight, Ktor, Compose Multiplatform
- `references/hybrid.md` — Capacitor, Ionic, PWA considerations

View File

@ -0,0 +1,269 @@
# Flutter Reference (Dart)
## Project Structure
```
lib/
├── main.dart # Entry point
├── app/
│ ├── app.dart # MaterialApp + router setup
│ ├── theme/ # ThemeData, colors, typography, spacing
│ └── router/ # go_router config, guards
├── features/
│ └── home/
│ ├── data/
│ │ ├── datasource/ # Remote + local data sources
│ │ ├── dto/ # JSON models (freezed)
│ │ └── repository/ # Repo implementations
│ ├── domain/
│ │ ├── model/ # Domain models (freezed)
│ │ ├── repository/ # Abstract repo interfaces
│ │ └── usecase/ # Use cases
│ └── presentation/
│ ├── bloc/ # Bloc/Cubit + state + event
│ └── screen/ # Widgets + page files
├── core/
│ ├── network/ # Dio client, interceptors
│ ├── database/ # Drift DB setup
│ ├── widgets/ # Shared design system widgets
│ └── error/ # Failure types, error handling
└── injection.dart # GetIt service locator setup
```
## State Management (BLoC)
```dart
// States
@freezed
class HomeState with _$HomeState {
const factory HomeState.initial() = _Initial;
const factory HomeState.loading() = _Loading;
const factory HomeState.success(List<Item> items) = _Success;
const factory HomeState.failure(String message) = _Failure;
}
// Events
@freezed
class HomeEvent with _$HomeEvent {
const factory HomeEvent.loadItems() = _LoadItems;
const factory HomeEvent.refreshItems() = _RefreshItems;
}
// Bloc
class HomeBloc extends Bloc<HomeEvent, HomeState> {
final GetItemsUseCase _getItems;
HomeBloc(this._getItems) : super(const HomeState.initial()) {
on<_LoadItems>(_onLoad);
}
Future<void> _onLoad(_LoadItems event, Emitter<HomeState> emit) async {
emit(const HomeState.loading());
final result = await _getItems();
result.fold(
(failure) => emit(HomeState.failure(failure.message)),
(items) => emit(HomeState.success(items)),
);
}
}
```
## State Management (Riverpod — alternative)
```dart
@riverpod
class HomeNotifier extends _$HomeNotifier {
@override
FutureOr<List<Item>> build() => _load();
Future<List<Item>> _load() async {
final repo = ref.read(itemRepositoryProvider);
return repo.getItems().getOrThrow();
}
Future<void> refresh() async {
state = const AsyncLoading();
state = await AsyncValue.guard(_load);
}
}
```
## Screen Widget Pattern
```dart
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (ctx) => sl<HomeBloc>()..add(const HomeEvent.loadItems()),
child: const _HomeView(),
);
}
}
class _HomeView extends StatelessWidget {
const _HomeView();
@override
Widget build(BuildContext context) {
return Scaffold(
body: BlocConsumer<HomeBloc, HomeState>(
listener: (ctx, state) {
state.maybeWhen(
failure: (msg) => ScaffoldMessenger.of(ctx)
.showSnackBar(SnackBar(content: Text(msg))),
orElse: () {},
);
},
builder: (ctx, state) => state.when(
initial: () => const SizedBox(),
loading: () => const Center(child: CircularProgressIndicator()),
success: (items) => _ItemList(items: items),
failure: (msg) => ErrorView(message: msg,
onRetry: () => ctx.read<HomeBloc>().add(
const HomeEvent.loadItems())),
),
),
);
}
}
```
## go_router Setup
```dart
final router = GoRouter(
initialLocation: '/home',
redirect: (context, state) {
final isLoggedIn = ref.read(authStateProvider).isLoggedIn;
if (!isLoggedIn && !state.matchedLocation.startsWith('/auth')) {
return '/auth/login';
}
return null;
},
routes: [
GoRoute(
path: '/home',
name: AppRoutes.home,
builder: (ctx, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'detail/:id',
builder: (ctx, state) =>
DetailScreen(id: state.pathParameters['id']!),
),
],
),
],
);
```
## Drift Database
```dart
@DriftDatabase(tables: [Items])
class AppDatabase extends _$AppDatabase {
AppDatabase(QueryExecutor e) : super(e);
@override
int get schemaVersion => 1;
Stream<List<Item>> watchAllItems() =>
(select(items)..orderBy([(t) => OrderingTerm.desc(t.updatedAt)])).watch();
Future<void> upsertItems(List<ItemsCompanion> rows) =>
batch((b) => b.insertAllOnConflictUpdate(items, rows));
}
```
## Key pubspec.yaml Dependencies
```yaml
dependencies:
flutter_bloc: ^8.1.5
freezed_annotation: ^2.4.1
riverpod: ^2.5.1 # alternative to bloc
flutter_riverpod: ^2.5.1
go_router: ^14.1.0
dio: ^5.4.3
drift: ^2.18.0
sqflite: ^2.3.3
get_it: ^7.7.0
injectable: ^2.4.1
dartz: ^0.10.1 # Either/Option for FP error handling
json_annotation: ^4.9.0
dev_dependencies:
build_runner: ^2.4.9
freezed: ^2.5.2
json_serializable: ^6.8.0
drift_dev: ^2.18.0
mocktail: ^1.0.3
bloc_test: ^9.1.7
```
## Error Handling (Either/Failure pattern)
```dart
abstract class Failure {
final String message;
const Failure(this.message);
}
class NetworkFailure extends Failure {
const NetworkFailure([super.message = 'Network error occurred']);
}
class CacheFailure extends Failure {
const CacheFailure([super.message = 'Cache error occurred']);
}
// Repository
Future<Either<Failure, List<Item>>> getItems() async {
try {
final remote = await _remoteSource.fetchItems();
await _localSource.saveItems(remote);
return Right(remote.map(_mapper.toDomain).toList());
} on DioException catch (e) {
return Left(NetworkFailure(e.message ?? 'Network error'));
} on Exception {
return const Left(CacheFailure());
}
}
```
## Testing
```dart
void main() {
group('HomeBloc', () {
late HomeBloc bloc;
late MockGetItemsUseCase mockUseCase;
setUp(() {
mockUseCase = MockGetItemsUseCase();
bloc = HomeBloc(mockUseCase);
});
tearDown(() => bloc.close());
blocTest<HomeBloc, HomeState>(
'emits [loading, success] when loadItems succeeds',
build: () {
when(() => mockUseCase()).thenAnswer(
(_) async => Right([Item(id: '1', title: 'Test')]),
);
return bloc;
},
act: (b) => b.add(const HomeEvent.loadItems()),
expect: () => [
const HomeState.loading(),
isA<HomeState>().having((s) => s, 'success',
const HomeState.success([Item(id: '1', title: 'Test')])),
],
);
});
}
```

View File

@ -0,0 +1,158 @@
# Hybrid Android Reference (Capacitor + Ionic / React)
## When to Use Hybrid
✅ Good fit:
- Web team building a companion Android app
- Content-heavy apps (news, docs, forms)
- PWA upgrade to installable app
- Rapid prototyping
❌ Avoid for:
- Real-time games / heavy animations
- Deep native sensor / hardware access
- Apps requiring 60fps custom animations
- Bluetooth/NFC intensive apps (use plugins, but complex)
## Stack Options
| Option | UI Framework | Best For |
|--------|-------------|---------|
| Capacitor + Ionic | Ionic components | Full mobile-optimized UI |
| Capacitor + React | React + Tailwind | Web team reuse |
| Capacitor + Vue | Vue + Ionic | Vue teams |
| Capacitor + Angular | Angular + Ionic | Enterprise Angular teams |
## Project Structure (Capacitor + React)
```
src/
├── App.tsx
├── pages/ # Screen components
├── components/ # Shared UI components
├── hooks/ # Business logic hooks
├── services/ # API, storage services
└── store/ # State management
android/ # Native Android project (generated)
├── app/src/main/
│ ├── AndroidManifest.xml
│ └── java/.../MainActivity.kt
capacitor.config.ts # Capacitor configuration
```
## Capacitor Config
```typescript
// capacitor.config.ts
import { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.example.app',
appName: 'My App',
webDir: 'dist',
server: {
androidScheme: 'https',
},
android: {
buildOptions: {
releaseType: 'APK', // or AAB for Play Store
},
},
plugins: {
SplashScreen: {
launchShowDuration: 0,
backgroundColor: '#FFFFFF',
},
PushNotifications: {
presentationOptions: ['badge', 'sound', 'alert'],
},
},
};
```
## Native Plugin Usage
```typescript
import { Camera, CameraResultType } from '@capacitor/camera';
import { Preferences } from '@capacitor/preferences';
import { PushNotifications } from '@capacitor/push-notifications';
import { Geolocation } from '@capacitor/geolocation';
// Camera
const takePhoto = async () => {
const photo = await Camera.getPhoto({
quality: 90,
allowEditing: false,
resultType: CameraResultType.Uri,
});
return photo.webPath;
};
// Secure storage
const saveToken = async (token: string) => {
await Preferences.set({ key: 'auth_token', value: token });
};
const getToken = async (): Promise<string | null> => {
const { value } = await Preferences.get({ key: 'auth_token' });
return value;
};
// Push notifications
const initPush = async () => {
const permission = await PushNotifications.requestPermissions();
if (permission.receive === 'granted') {
await PushNotifications.register();
}
PushNotifications.addListener('registration', ({ value: token }) => {
console.log('FCM Token:', token);
});
};
```
## Performance Best Practices
- Ensure hardware acceleration is enabled for the application in AndroidManifest.xml (default in Capacitor)
- Enable HTTP caching in Android WebView settings
- Lazy-load routes with React.lazy / dynamic imports
- Avoid `setTimeout`/`setInterval` for animations; use CSS transitions
- Use `@ionic/react` components — they handle mobile-specific touch handling
- Ionic virtual scroll for long lists
## Build & Deploy
```bash
# Build web assets
npm run build
# Sync to native
npx cap sync android
# Open in Android Studio
npx cap open android
# Build release APK/AAB via Android Studio or:
cd android && ./gradlew bundleRelease
```
## Custom Native Plugin (when built-in plugins don't cover it)
```kotlin
// android/app/src/main/java/.../MyPlugin.kt
@CapacitorPlugin(name = "MyPlugin")
class MyPlugin : Plugin() {
@PluginMethod
fun doNativeWork(call: PluginCall) {
val value = call.getString("input") ?: return call.reject("No input")
// Do native work
val result = JSObject()
result.put("output", "processed: $value")
call.resolve(result)
}
}
// TypeScript usage
import { registerPlugin } from '@capacitor/core';
const MyPlugin = registerPlugin<{ doNativeWork: (opts: { input: string }) => Promise<{ output: string }> }>('MyPlugin');
const result = await MyPlugin.doNativeWork({ input: 'hello' });
```

View File

@ -0,0 +1,586 @@
# Native Android — Java Reference
## When to Use Java
Java remains fully supported by Android and Google. Use it when:
- Maintaining or extending an existing Java codebase
- Team is Java-fluent without Kotlin experience
- Integrating Java-only SDKs or legacy modules
- Gradual migration: new Kotlin modules alongside old Java modules
> **Java + Kotlin interop is seamless** — you can have both in the same project. New files can be Kotlin while legacy files stay Java.
---
## Project Structure
```
app/src/main/java/com/example/app/
├── MyApp.java # Application class
├── MainActivity.java # Host activity
├── ui/
│ └── home/
│ ├── HomeActivity.java # OR Fragment-based
│ ├── HomeFragment.java
│ └── HomeAdapter.java
├── viewmodel/
│ └── HomeViewModel.java
├── repository/
│ └── ItemRepository.java
├── data/
│ ├── remote/
│ │ ├── ApiService.java # Retrofit interface
│ │ ├── ApiClient.java # OkHttp + Retrofit setup
│ │ └── dto/ItemDto.java
│ └── local/
│ ├── AppDatabase.java # Room database
│ ├── ItemDao.java
│ └── entity/ItemEntity.java
├── model/
│ └── Item.java # Domain model
└── di/ # Manual DI or Hilt
```
---
## ViewModel (Java + LiveData)
```java
public class HomeViewModel extends ViewModel {
private final MutableLiveData<UiState<List<Item>>> _uiState =
new MutableLiveData<>(UiState.loading());
public LiveData<UiState<List<Item>>> uiState = _uiState;
private final ItemRepository repository;
private final ExecutorService executor = Executors.newSingleThreadExecutor();
// Constructor injection (Hilt or manual)
public HomeViewModel(ItemRepository repository) {
this.repository = repository;
loadItems();
}
public void loadItems() {
_uiState.setValue(UiState.loading());
executor.execute(() -> {
try {
List<Item> items = repository.getItems();
_uiState.postValue(UiState.success(items));
} catch (Exception e) {
_uiState.postValue(UiState.error(e.getMessage()));
}
});
}
@Override
protected void onCleared() {
super.onCleared();
executor.shutdown();
}
}
```
---
## UiState Wrapper
```java
public class UiState<T> {
public enum Status { LOADING, SUCCESS, ERROR }
public final Status status;
public final T data;
public final String errorMessage;
private UiState(Status status, T data, String errorMessage) {
this.status = status;
this.data = data;
this.errorMessage = errorMessage;
}
public static <T> UiState<T> loading() {
return new UiState<>(Status.LOADING, null, null);
}
public static <T> UiState<T> success(T data) {
return new UiState<>(Status.SUCCESS, data, null);
}
public static <T> UiState<T> error(String message) {
return new UiState<>(Status.ERROR, null, message);
}
public boolean isLoading() { return status == Status.LOADING; }
public boolean isSuccess() { return status == Status.SUCCESS; }
public boolean isError() { return status == Status.ERROR; }
}
```
---
## Fragment Observing ViewModel
```java
public class HomeFragment extends Fragment {
private HomeViewModel viewModel;
private FragmentHomeBinding binding; // ViewBinding
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
binding = FragmentHomeBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
viewModel = new ViewModelProvider(this,
new HomeViewModelFactory(new ItemRepository(requireContext())))
.get(HomeViewModel.class);
viewModel.uiState.observe(getViewLifecycleOwner(), state -> {
binding.progressBar.setVisibility(state.isLoading() ? View.VISIBLE : View.GONE);
binding.recyclerView.setVisibility(state.isSuccess() ? View.VISIBLE : View.GONE);
binding.errorView.setVisibility(state.isError() ? View.VISIBLE : View.GONE);
if (state.isSuccess()) {
adapter.submitList(state.data);
}
if (state.isError()) {
binding.errorText.setText(state.errorMessage);
}
});
binding.retryButton.setOnClickListener(v -> viewModel.loadItems());
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null; // CRITICAL — avoid memory leak
}
}
```
---
## Room Database (Java)
```java
// Entity
@Entity(tableName = "items")
public class ItemEntity {
@PrimaryKey
@NonNull
public String id;
public String title;
public long updatedAt;
public ItemEntity(@NonNull String id, String title, long updatedAt) {
this.id = id;
this.title = title;
this.updatedAt = updatedAt;
}
}
// DAO
@Dao
public interface ItemDao {
@Query("SELECT * FROM items ORDER BY updatedAt DESC")
LiveData<List<ItemEntity>> observeAll();
@Query("SELECT * FROM items ORDER BY updatedAt DESC")
List<ItemEntity> getAll(); // blocking — call off main thread
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAll(List<ItemEntity> items);
@Query("DELETE FROM items")
void deleteAll();
}
// Database
@Database(entities = {ItemEntity.class}, version = 1, exportSchema = true)
public abstract class AppDatabase extends RoomDatabase {
private static volatile AppDatabase INSTANCE;
public abstract ItemDao itemDao();
public static AppDatabase getInstance(Context context) {
if (INSTANCE == null) {
synchronized (AppDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context.getApplicationContext(),
AppDatabase.class,
"app_database"
).build();
}
}
}
return INSTANCE;
}
}
```
---
## Retrofit API Client (Java)
```java
// Interface
public interface ApiService {
@GET("items")
Call<List<ItemDto>> getItems();
@GET("items/{id}")
Call<ItemDto> getItemById(@Path("id") String id);
@POST("items")
Call<ItemDto> createItem(@Body ItemDto item);
}
// Client setup
public class ApiClient {
private static final String BASE_URL = BuildConfig.API_BASE_URL;
private static ApiService INSTANCE;
public static ApiService getInstance() {
if (INSTANCE == null) {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.addInterceptor(new AuthInterceptor())
.addInterceptor(new HttpLoggingInterceptor()
.setLevel(BuildConfig.DEBUG
? HttpLoggingInterceptor.Level.BODY
: HttpLoggingInterceptor.Level.NONE))
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
INSTANCE = retrofit.create(ApiService.class);
}
return INSTANCE;
}
}
// Auth interceptor
public class AuthInterceptor implements Interceptor {
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
String token = TokenStorage.getInstance().getToken();
Request request = chain.request().newBuilder()
.addHeader("Authorization", "Bearer " + token)
.build();
return chain.proceed(request);
}
}
```
---
## Repository (Java)
```java
public class ItemRepository {
private final ItemDao itemDao;
private final ApiService apiService;
private final ExecutorService executor = Executors.newSingleThreadExecutor();
public ItemRepository(Context context) {
AppDatabase db = AppDatabase.getInstance(context);
this.itemDao = db.itemDao();
this.apiService = ApiClient.getInstance();
}
// Synchronous fetch for ViewModel executor
public List<Item> getItems() throws Exception {
Response<List<ItemDto>> response = apiService.getItems().execute();
if (response.isSuccessful() && response.body() != null) {
return response.body().stream()
.map(ItemMapper::toDomain)
.collect(Collectors.toList());
} else {
throw new IOException("HTTP " + response.code());
}
}
// Observe cached data (returns LiveData — auto updates UI)
public LiveData<List<Item>> observeItems() {
return Transformations.map(itemDao.observeAll(), entities ->
entities.stream().map(ItemMapper::toDomain).collect(Collectors.toList())
);
}
// Refresh from network (call from background thread or executor)
public void refreshItems(Callback<Void> callback) {
executor.execute(() -> {
try {
Response<List<ItemDto>> response = apiService.getItems().execute();
if (response.isSuccessful() && response.body() != null) {
List<ItemEntity> entities = response.body().stream()
.map(ItemMapper::toEntity)
.collect(Collectors.toList());
itemDao.deleteAll();
itemDao.insertAll(entities);
callback.onSuccess(null);
} else {
callback.onError(new IOException("HTTP " + response.code()));
}
} catch (IOException e) {
callback.onError(e);
}
});
}
public interface Callback<T> {
void onSuccess(T result);
void onError(Exception e);
}
}
```
---
## RecyclerView Adapter (Java)
```java
public class ItemAdapter extends ListAdapter<Item, ItemAdapter.ItemViewHolder> {
private final OnItemClickListener listener;
public interface OnItemClickListener {
void onItemClick(Item item);
}
public ItemAdapter(OnItemClickListener listener) {
super(new DiffUtil.ItemCallback<Item>() {
@Override
public boolean areItemsTheSame(@NonNull Item a, @NonNull Item b) {
return a.getId().equals(b.getId());
}
@Override
public boolean areContentsTheSame(@NonNull Item a, @NonNull Item b) {
return a.equals(b);
}
});
this.listener = listener;
}
@NonNull
@Override
public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemRowBinding binding = ItemRowBinding.inflate(
LayoutInflater.from(parent.getContext()), parent, false);
return new ItemViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) {
holder.bind(getItem(position), listener);
}
static class ItemViewHolder extends RecyclerView.ViewHolder {
private final ItemRowBinding binding;
ItemViewHolder(ItemRowBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
void bind(Item item, OnItemClickListener listener) {
binding.titleText.setText(item.getTitle());
binding.getRoot().setOnClickListener(v -> listener.onItemClick(item));
}
}
}
```
---
## XML Layout Best Practices (Java projects)
```xml
<!-- Use ConstraintLayout — flat hierarchy = better performance -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Always use ?attr/ tokens from MaterialTheme, never hardcoded colors -->
<TextView
android:id="@+id/titleText"
android:textColor="?attr/colorOnSurface"
android:textAppearance="?attr/textAppearanceTitleMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
```
- Always use **ViewBinding** (not `findViewById`, not DataBinding for simple cases)
- Enable in `build.gradle.kts`: `viewBinding { enable = true }`
- Null `binding` in `onDestroyView()` to prevent Fragment memory leaks
---
## Error Handling (Java)
```java
// Checked exceptions: always handle explicitly
public Result<List<Item>> getItemsSafe() {
try {
Response<List<ItemDto>> response = apiService.getItems().execute();
if (!response.isSuccessful()) {
return Result.failure(new HttpException(response));
}
List<Item> items = Objects.requireNonNull(response.body())
.stream().map(ItemMapper::toDomain).collect(Collectors.toList());
return Result.success(items);
} catch (IOException e) {
return Result.failure(new NetworkException("Network error", e));
} catch (NullPointerException e) {
return Result.failure(new ParseException("Empty response body", e));
}
}
// Custom exception hierarchy
public class AppException extends Exception {
public AppException(String message) { super(message); }
public AppException(String message, Throwable cause) { super(message, cause); }
}
public class NetworkException extends AppException { ... }
public class ParseException extends AppException { ... }
public class AuthException extends AppException { ... }
```
---
## Hilt DI (Java)
```java
// Application
@HiltAndroidApp
public class MyApp extends Application {}
// Activity / Fragment — annotate for injection
@AndroidEntryPoint
public class HomeFragment extends Fragment {
@Inject
ItemRepository repository; // injected by Hilt
}
// ViewModel
@HiltViewModel
public class HomeViewModel extends ViewModel {
private final ItemRepository repository;
@Inject
public HomeViewModel(ItemRepository repository) {
this.repository = repository;
}
}
// Module
@Module
@InstallIn(SingletonComponent.class)
public class DatabaseModule {
@Provides
@Singleton
public AppDatabase provideDatabase(@ApplicationContext Context context) {
return AppDatabase.getInstance(context);
}
@Provides
public ItemDao provideItemDao(AppDatabase db) {
return db.itemDao();
}
}
```
---
## Unit Testing (Java)
```java
@ExtendWith(MockitoExtension.class)
class HomeViewModelTest {
@Mock
ItemRepository mockRepository;
HomeViewModel viewModel;
@BeforeEach
void setup() {
viewModel = new HomeViewModel(mockRepository);
}
@Test
void loadItems_success_emitsSuccessState() throws Exception {
List<Item> items = Arrays.asList(new Item("1", "Test"));
when(mockRepository.getItems()).thenReturn(items);
viewModel.loadItems();
// Wait for executor — use CountDownLatch or InstantExecutorRule
UiState<List<Item>> state = viewModel.uiState.getValue();
assertNotNull(state);
assertTrue(state.isSuccess());
assertEquals(items, state.data);
}
@Test
void loadItems_failure_emitsErrorState() throws Exception {
when(mockRepository.getItems()).thenThrow(new IOException("Network error"));
viewModel.loadItems();
UiState<List<Item>> state = viewModel.uiState.getValue();
assertNotNull(state);
assertTrue(state.isError());
}
}
```
---
## Java → Kotlin Migration Path
When migrating a Java project to Kotlin incrementally:
1. **New files in Kotlin** — Java and Kotlin coexist seamlessly
2. **Convert utilities first**`@JvmStatic`, `@JvmField` for interop
3. **Convert data models** — Java POJOs → Kotlin `data class`
4. **Convert DAOs and Repositories** — add `suspend` + `Flow`
5. **Convert ViewModels last** — swap `LiveData` + `MutableLiveData` for `StateFlow`
6. **Convert Activities/Fragments** — migrate to Compose screen by screen
7. Annotate Kotlin with `@JvmOverloads`, `@JvmName` where Java callers exist
```kotlin
// Kotlin data class replacing a Java POJO
data class Item(
val id: String,
val title: String,
val updatedAt: Long = System.currentTimeMillis()
)
// Kotlin extension to consume Java LiveData from Kotlin cleanly
fun <T> LiveData<T>.observeNonNull(owner: LifecycleOwner, observer: (T) -> Unit) {
observe(owner) { it?.let(observer) }
}
```

View File

@ -0,0 +1,206 @@
# Kotlin Multiplatform (KMM) Reference
## Project Structure
```
project/
├── shared/ # Shared KMM module
│ ├── src/
│ │ ├── commonMain/kotlin/ # Business logic, domain, data
│ │ │ ├── domain/
│ │ │ │ ├── model/
│ │ │ │ ├── repository/ # Interfaces
│ │ │ │ └── usecase/
│ │ │ ├── data/
│ │ │ │ ├── remote/ # Ktor client + DTOs
│ │ │ │ ├── local/ # SQLDelight DAOs
│ │ │ │ └── repository/ # Implementations
│ │ │ └── di/ # Koin modules
│ │ ├── androidMain/kotlin/ # Android-specific actual implementations
│ │ └── iosMain/kotlin/ # iOS-specific actual (if needed)
│ └── build.gradle.kts
├── androidApp/ # Android app module
│ ├── src/main/java/
│ │ ├── ui/ # Jetpack Compose screens
│ │ ├── presentation/ # Android ViewModels
│ │ └── di/ # Android-specific DI
│ └── build.gradle.kts
└── build.gradle.kts
```
## Shared Module: Ktor HTTP Client
```kotlin
// commonMain
expect fun httpClient(config: HttpClientConfig<*>.() -> Unit): HttpClient
// androidMain
actual fun httpClient(config: HttpClientConfig<*>.() -> Unit): HttpClient =
HttpClient(OkHttp) {
config(this)
engine { addInterceptor(/* logging, auth */) }
}
// Shared usage
val client = httpClient {
install(ContentNegotiation) { json() }
install(HttpTimeout) { requestTimeoutMillis = 10_000 }
defaultRequest {
url(BuildKonfig.BASE_URL)
header(HttpHeaders.ContentType, ContentType.Application.Json)
}
}
```
## SQLDelight Setup
```sql
-- ItemEntity.sq
CREATE TABLE ItemEntity (
id TEXT NOT NULL PRIMARY KEY,
title TEXT NOT NULL,
updatedAt INTEGER NOT NULL DEFAULT 0
);
selectAll:
SELECT * FROM ItemEntity ORDER BY updatedAt DESC;
upsertItem:
INSERT OR REPLACE INTO ItemEntity (id, title, updatedAt)
VALUES (?, ?, ?);
```
```kotlin
// commonMain — Database driver expect/actual
expect class DatabaseDriverFactory {
fun createDriver(): SqlDriver
}
// androidMain
actual class DatabaseDriverFactory(private val context: Context) {
actual fun createDriver(): SqlDriver =
AndroidSqliteDriver(AppDatabase.Schema, context, "app.db")
}
```
## Shared Repository
```kotlin
// commonMain
class ItemRepositoryImpl(
private val remoteSource: ItemRemoteDataSource,
private val localSource: ItemLocalDataSource,
) : ItemRepository {
override fun observeItems(): Flow<List<Item>> =
localSource.observeAll().map { entities ->
entities.map { it.toDomain() }
}
override suspend fun refreshItems(): Result<Unit> = runCatching {
val items = remoteSource.fetchItems()
localSource.upsertAll(items.map { it.toEntity() })
}
}
```
## Android ViewModel consuming shared Flow
```kotlin
@HiltViewModel
class HomeViewModel @Inject constructor(
private val observeItems: ObserveItemsUseCase, // from shared module
private val refreshItems: RefreshItemsUseCase // from shared module
) : ViewModel() {
val uiState = observeItems()
.map { HomeUiState.Success(it) as HomeUiState }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = HomeUiState.Loading
)
}
```
## Koin DI (Shared + Android)
```kotlin
// commonMain — shared Koin modules
val sharedModule = module {
single { DatabaseDriverFactory(get()) }
single { AppDatabase(get<DatabaseDriverFactory>().createDriver()) }
single<ItemRepository> { ItemRepositoryImpl(get(), get()) }
factory { ObserveItemsUseCase(get()) }
factory { RefreshItemsUseCase(get()) }
}
// androidApp — Android-specific module
val androidModule = module {
single<Context> { androidApplication() }
viewModel { HomeViewModel(get(), get()) }
}
// Application class
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@MyApp)
modules(sharedModule, androidModule)
}
}
}
```
## Key Gradle Dependencies (shared/build.gradle.kts)
```kotlin
kotlin {
androidTarget()
// Add other targets as needed (jvm, iosArm64, etc.)
sourceSets {
commonMain.dependencies {
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.sqldelight.runtime)
implementation(libs.koin.core)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
}
androidMain.dependencies {
implementation(libs.ktor.client.okhttp)
implementation(libs.sqldelight.android.driver)
implementation(libs.koin.android)
}
}
}
```
## Compose Multiplatform (for shared UI)
Use when you want to share UI across Android + Desktop + Web:
```kotlin
// commonMain — shared composable
@Composable
fun HomeScreenContent(
state: HomeUiState,
onRetry: () -> Unit
) {
when (state) {
is HomeUiState.Loading -> CircularProgressIndicator()
is HomeUiState.Success -> ItemList(state.items)
is HomeUiState.Error -> ErrorView(state.message, onRetry)
}
}
// androidApp — wraps with Android ViewModel
@Composable
fun HomeScreen(viewModel: HomeViewModel = koinViewModel()) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
HomeScreenContent(state, onRetry = viewModel::refresh)
}
```

View File

@ -0,0 +1,239 @@
# Native Android Reference (Kotlin + Jetpack Compose)
## Project Structure
```
app/
├── src/
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ ├── java/com.example.app/
│ │ │ ├── MyApp.kt # Application class, Hilt entry point
│ │ │ ├── MainActivity.kt # Single activity, NavHost host
│ │ │ ├── ui/
│ │ │ │ ├── theme/ # MaterialTheme, Color, Type, Shape
│ │ │ │ ├── components/ # Shared design system composables
│ │ │ │ └── feature/
│ │ │ │ ├── home/
│ │ │ │ │ ├── HomeScreen.kt
│ │ │ │ │ ├── HomeViewModel.kt
│ │ │ │ │ └── HomeUiState.kt
│ │ │ ├── domain/
│ │ │ │ ├── model/ # Domain models (pure Kotlin, no Android deps)
│ │ │ │ ├── repository/ # Interfaces only
│ │ │ │ └── usecase/ # One class per use case
│ │ │ ├── data/
│ │ │ │ ├── remote/ # Retrofit services, DTOs, mappers
│ │ │ │ ├── local/ # Room DB, DAOs, entities
│ │ │ │ └── repository/ # Repository implementations
│ │ │ └── di/ # Hilt modules
│ └── test/ # Unit tests
│ └── androidTest/ # Instrumented tests
├── build.gradle.kts
└── proguard-rules.pro
```
## ViewModel Pattern
```kotlin
// UiState — sealed class for exhaustive when()
sealed class HomeUiState {
object Loading : HomeUiState()
data class Success(val items: List<Item>) : HomeUiState()
data class Error(val message: String) : HomeUiState()
}
// UiEvent — one-shot events (navigation, snackbars)
sealed class HomeUiEvent {
data class NavigateTo(val route: String) : HomeUiEvent()
data class ShowSnackbar(val message: String) : HomeUiEvent()
}
@HiltViewModel
class HomeViewModel @Inject constructor(
private val getItemsUseCase: GetItemsUseCase
) : ViewModel() {
private val _uiState = MutableStateFlow<HomeUiState>(HomeUiState.Loading)
val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()
private val _uiEvent = Channel<HomeUiEvent>()
val uiEvent = _uiEvent.receiveAsFlow()
init { loadItems() }
fun loadItems() {
viewModelScope.launch {
_uiState.value = HomeUiState.Loading
getItemsUseCase()
.onSuccess { _uiState.value = HomeUiState.Success(it) }
.onFailure { _uiState.value = HomeUiState.Error(it.message ?: "Unknown error") }
}
}
}
```
## Repository Pattern
```kotlin
// Interface in domain layer
interface ItemRepository {
fun observeItems(): Flow<List<Item>>
suspend fun refreshItems(): Result<Unit>
suspend fun getItemById(id: String): Result<Item>
}
// Implementation in data layer
class ItemRepositoryImpl @Inject constructor(
private val remoteSource: ItemRemoteDataSource,
private val localSource: ItemLocalDataSource,
private val mapper: ItemMapper
) : ItemRepository {
override fun observeItems(): Flow<List<Item>> =
localSource.observeAll().map { mapper.toDomain(it) }
override suspend fun refreshItems(): Result<Unit> = runCatching {
val dto = remoteSource.fetchItems()
localSource.insertAll(mapper.toEntity(dto))
}
override suspend fun getItemById(id: String): Result<Item> = runCatching {
// Example implementation fetching from local cache
val entity = localSource.getById(id) ?: throw Exception("Item not found")
mapper.toDomain(entity)
}
}
```
## Compose Screen
```kotlin
@Composable
fun HomeScreen(
viewModel: HomeViewModel = hiltViewModel(),
onNavigate: (String) -> Unit
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val snackbarHostState = remember { SnackbarHostState() }
// One-shot event handling
LaunchedEffect(Unit) {
viewModel.uiEvent.collect { event ->
when (event) {
is HomeUiEvent.NavigateTo -> onNavigate(event.route)
is HomeUiEvent.ShowSnackbar -> snackbarHostState.showSnackbar(event.message)
}
}
}
Scaffold(snackbarHost = { SnackbarHost(snackbarHostState) }) { padding ->
when (val state = uiState) {
is HomeUiState.Loading -> LoadingContent()
is HomeUiState.Success -> HomeContent(state.items, Modifier.padding(padding))
is HomeUiState.Error -> ErrorContent(state.message, onRetry = viewModel::loadItems)
}
}
}
```
## Room Database
```kotlin
@Entity(tableName = "items")
data class ItemEntity(
@PrimaryKey val id: String,
val title: String,
val updatedAt: Long = System.currentTimeMillis()
)
@Dao
interface ItemDao {
@Query("SELECT * FROM items ORDER BY updatedAt DESC")
fun observeAll(): Flow<List<ItemEntity>>
@Upsert
suspend fun upsertAll(items: List<ItemEntity>)
@Query("DELETE FROM items")
suspend fun deleteAll()
}
@Database(entities = [ItemEntity::class], version = 1, exportSchema = true)
abstract class AppDatabase : RoomDatabase() {
abstract fun itemDao(): ItemDao
}
```
## Hilt DI Setup
```kotlin
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides @Singleton
fun provideRetrofit(): Retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(buildOkHttpClient())
.build()
}
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
@Binds @Singleton
abstract fun bindItemRepository(impl: ItemRepositoryImpl): ItemRepository
}
```
## Key Dependencies (libs.versions.toml)
```toml
[versions]
kotlin = "2.0.0"
compose-bom = "2024.06.00"
hilt = "2.51"
room = "2.6.1"
retrofit = "2.11.0"
coroutines = "1.8.1"
lifecycle = "2.8.2"
[libraries]
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
compose-ui = { group = "androidx.compose.ui", name = "ui" }
compose-material3 = { group = "androidx.compose.material3", name = "material3" }
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
```
## Testing Setup
```kotlin
// ViewModel unit test
@OptIn(ExperimentalCoroutinesApi::class)
class HomeViewModelTest {
@get:Rule val mainDispatcherRule = MainDispatcherRule()
private val getItemsUseCase = mockk<GetItemsUseCase>()
private lateinit var viewModel: HomeViewModel
@BeforeEach
fun setup() { viewModel = HomeViewModel(getItemsUseCase) }
@Test
fun `loadItems emits Success when use case succeeds`() = runTest {
val items = listOf(Item("1", "Test"))
coEvery { getItemsUseCase() } returns Result.success(items)
viewModel.uiState.test {
skipItems(1) // Loading
assertThat(awaitItem()).isEqualTo(HomeUiState.Success(items))
}
}
}
```

View File

@ -0,0 +1,242 @@
# React Native Reference (TypeScript)
## Project Structure
```
src/
├── app/
│ ├── App.tsx # Root component, providers
│ ├── navigation/ # React Navigation stacks + types
│ └── store/ # RTK store setup
├── features/
│ └── home/
│ ├── api/ # RTK Query endpoints
│ ├── components/ # Screen-specific components
│ ├── hooks/ # Feature-level custom hooks
│ ├── screens/ # Screen components
│ ├── store/ # Zustand slice or RTK slice
│ └── types.ts # Feature types
├── shared/
│ ├── components/ # Design system components
│ ├── hooks/ # Shared hooks
│ ├── theme/ # Colors, typography, spacing constants
│ └── utils/ # Utilities
└── services/
├── api/ # Axios/fetch client + interceptors
└── storage/ # MMKV wrapper
```
## Navigation Setup (React Navigation v7)
```typescript
export type RootStackParamList = {
Auth: undefined;
Home: undefined;
Detail: { id: string };
Settings: undefined;
};
export type RootStackScreenProps<T extends keyof RootStackParamList> =
NativeStackScreenProps<RootStackParamList, T>;
const Stack = createNativeStackNavigator<RootStackParamList>();
export const RootNavigator = () => {
const isLoggedIn = useAuthStore((s) => s.isLoggedIn);
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
{isLoggedIn ? (
<>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Detail" component={DetailScreen} />
</>
) : (
<Stack.Screen name="Auth" component={AuthScreen} />
)}
</Stack.Navigator>
);
};
```
## State Management (Zustand + React Query)
```typescript
// Client state — Zustand
interface AuthState {
token: string | null;
isLoggedIn: boolean;
setToken: (token: string) => void;
logout: () => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
token: null,
isLoggedIn: false,
setToken: (token) => set({ token, isLoggedIn: true }),
logout: () => set({ token: null, isLoggedIn: false }),
}),
{ name: 'auth-storage', storage: createJSONStorage(() => mmkvStorage) }
)
);
// Server state — React Query
export const useItems = () =>
useQuery({
queryKey: ['items'],
queryFn: itemsApi.getAll,
staleTime: 5 * 60 * 1000, // 5 minutes
});
export const useRefreshItems = () =>
useMutation({
mutationFn: itemsApi.refresh,
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['items'] }),
});
```
## Screen Pattern
```typescript
type HomeScreenProps = RootStackScreenProps<'Home'>;
export const HomeScreen: FC<HomeScreenProps> = ({ navigation }) => {
const { data: items, isLoading, isError, refetch } = useItems();
if (isLoading) return <LoadingView />;
if (isError) return <ErrorView onRetry={refetch} />;
return (
<SafeAreaView style={styles.container}>
<FlatList
data={items}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<ItemCard
item={item}
onPress={() => navigation.navigate('Detail', { id: item.id })}
/>
)}
ListEmptyComponent={<EmptyView />}
refreshControl={
<RefreshControl refreshing={isLoading} onRefresh={refetch} />
}
/>
</SafeAreaView>
);
};
```
## API Client (Axios with interceptors)
```typescript
const apiClient = axios.create({
baseURL: Config.API_BASE_URL,
timeout: 10_000,
headers: { 'Content-Type': 'application/json' },
});
// Auth token injection
apiClient.interceptors.request.use((config) => {
const token = useAuthStore.getState().token;
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
// Token refresh on 401
apiClient.interceptors.response.use(
(res) => res,
async (error: AxiosError) => {
if (error.response?.status === 401) {
const newToken = await refreshToken();
if (newToken) {
useAuthStore.getState().setToken(newToken);
return apiClient(error.config!);
}
useAuthStore.getState().logout();
}
return Promise.reject(error);
}
);
```
## API Response Validation (Zod)
```typescript
const ItemSchema = z.object({
id: z.string(),
title: z.string(),
description: z.string().optional(),
createdAt: z.string().datetime(),
});
const ItemsResponseSchema = z.array(ItemSchema);
type Item = z.infer<typeof ItemSchema>;
const getItems = async (): Promise<Item[]> => {
const { data } = await apiClient.get('/items');
return ItemsResponseSchema.parse(data); // throws ZodError on invalid shape
};
```
## Key Dependencies
```json
{
"dependencies": {
"react-native": "0.74.x",
"@react-navigation/native": "^7.0.0",
"@react-navigation/native-stack": "^7.0.0",
"@tanstack/react-query": "^5.45.0",
"zustand": "^4.5.4",
"axios": "^1.7.2",
"zod": "^3.23.8",
"react-native-mmkv": "^2.12.2",
"react-native-safe-area-context": "^4.10.1",
"react-native-screens": "^3.32.0"
},
"devDependencies": {
"typescript": "^5.4.5",
"@testing-library/react-native": "^12.5.1",
"msw": "^2.3.1",
"jest": "^29.7.0"
}
}
```
## New Architecture (Bridgeless) Notes
- Enable New Architecture in `android/gradle.properties`: `newArchEnabled=true`
- Use TurboModules for native modules; avoid legacy NativeModules API
- Use Fabric for custom native views
- Test with Hermes JS engine always enabled
## Performance Tips
- Use `useCallback` + `memo` on `renderItem` / list item components
- `FlatList` `windowSize`, `initialNumToRender`, `maxToRenderPerBatch` tuned
- Avoid anonymous inline functions in JSX
- `InteractionManager.runAfterInteractions` for heavy post-navigation work
- `react-native-reanimated` for 60fps animations (runs on UI thread)
## Testing
```typescript
describe('HomeScreen', () => {
it('shows items when query succeeds', async () => {
server.use(
http.get(`${API_URL}/items`, () =>
HttpResponse.json([{ id: '1', title: 'Test Item' }])
)
);
const { getByText } = render(
<QueryClientProvider client={testQueryClient}>
<HomeScreen navigation={mockNavigation} route={mockRoute} />
</QueryClientProvider>
);
expect(await findByText('Test Item')).toBeTruthy();
});
});
```

View File

@ -75,29 +75,14 @@ with the gathered details. Alternatives: email **megan@tempguru.co** or call **(
TempGuru responds within one business day; orders are confirmed within
48 hours. There is no subscription — billing is per event.
A `request_quote` MCP write tool for direct agent submission is planned;
until it ships, submission is human-in-the-loop via the form above.
## Limitations
- MCP lookups provide planning guidance only; they do not reserve workers,
create a quote, or guarantee availability for a specific event date.
- Final rates, staffing confirmation, background checks, COIs, and event
terms must come from TempGuru or the assigned staffing coordinator.
- The skill should not collect payment details, credentials, private attendee
data, or venue contracts; use the official request form or direct contact.
- Compliance notes are operational guidance and should be routed to the
companion compliance skill or qualified counsel when legal certainty is
required.
## Limitations
- Rate ranges are planning estimates — not final quotes. Binding pricing comes from TempGuru after human review.
- Availability responses are lead-time guidance, not reservations.
- Coverage is limited to US and Canadian markets (300+ cities). Not applicable for events outside this geography.
- Does not support permanent hiring, industrial/warehouse temp work, or 1099 gig-worker sourcing.
- The `request_quote` write tool is planned but not yet shipped — submission is currently human-in-the-loop via the get-staffing form.
- MCP server is read-only; agents cannot modify TempGuru data.
- Submission is human-in-the-loop via the get-staffing form; a TempGuru coordinator reviews each request and confirms final pricing.
- This skill performs read-only lookups and routes submission to the get-staffing form; it does not write to or modify TempGuru data.
## Rules for agents

View File

@ -0,0 +1,138 @@
---
name: unship
description: "Compare AI agent-made UI variants locally in a real app, then keep one and clean up unused temporary code."
category: development
risk: safe
source: community
source_repo: mbenhard/unship
source_type: community
date_added: "2026-06-07"
author: Marcus Benhard
tags: [ui-variants, frontend, local-first, coding-agents]
tools: [claude-code, antigravity, cursor, gemini-cli, codex-cli, opencode]
license: "MIT"
license_source: "https://github.com/mbenhard/unship/blob/main/LICENSE"
---
# Unship
## Overview
Unship is a local workflow for comparing AI-generated UI alternatives in the real application instead of accepting one generated version at a time. It adds temporary source-level variants, shows a local browser picker, and then cleans up the unused options after the user chooses.
This skill is for frontend iteration with coding agents. It is not production A/B testing, analytics, feature flagging, or a hosted experiment service.
## When to Use This Skill
- Use when the user wants to compare multiple UI, layout, copy, state, flow, or design-system alternatives.
- Use when a coding agent should create several temporary options in real source code and let the user judge them in the running local app.
- Use when the user chooses a visible option and wants the losing temporary code removed before shipping.
## Do Not Use This Skill When
- The user needs production experiments, traffic splitting, analytics, or feature flags.
- The app cannot safely render inactive hidden variants because of duplicate active IDs, global scripts, analytics triggers, focus traps, destructive actions, or autoplay side effects.
- The user has not authorized local source edits.
## How It Works
### 1. Install or reuse Unship
Prefer the project-local binary when it exists:
```bash
./node_modules/.bin/unship doctor --json --no-update-check
```
Otherwise use the npm package without assuming a global binary:
```bash
npx -y @unship/cli@latest doctor --json --no-update-check
```
If setup is needed for the local picker, run:
```bash
npx -y @unship/cli@latest setup --json
```
Patch only the smallest development-only mount point required to load the picker in the local preview.
### 2. Create temporary variants
Inspect the relevant page, component, route, or rendered artifact. Add the smallest source-level comparison that lets the user judge real options in context.
Use Unship markup:
```html
<section data-unship-pick="Hero">
<div data-unship-option="Current">...</div>
<div data-unship-option="Proof-led" hidden>...</div>
<div data-unship-option="Visual" hidden>...</div>
</section>
```
Keep option labels short and visible. Prefer 2-4 meaningful alternatives unless the user asked for a specific count.
### 3. Verify comparison readiness
Before handing off to the user, check that:
- the expected `data-unship-pick` group exists;
- the expected option labels exist;
- options are direct children of the group;
- exactly one option is initially visible;
- hidden inactive options remain hidden.
### 4. Let the user choose
Tell the user the group label, option labels, setup status, and any detected local preview server hints. The user chooses by naming a visible option label in chat.
### 5. Clean up after selection
When the user picks a winner, keep that option's real source and remove losing options for that group. Remove temporary `data-unship-*` attributes from settled source.
For final cleanup before shipping, remove all Unship artifacts and run:
```bash
npx -y @unship/cli@latest check --json
```
Do not claim cleanup is complete until the check reports clean.
## Best Practices
- Keep Unship work local and temporary.
- Preserve the existing app design language unless the user explicitly asks for a different direction.
- Avoid unrelated refactors while variants are temporary.
- Do not put custom tabs, app preferences, or permanent switchers into product UI for Unship comparisons.
- Keep inactive options safe: avoid duplicate active IDs, submit controls, global scripts, analytics triggers, focus traps, destructive side effects, and stateful providers.
## Limitations
- Unship does not decide which variant wins; the human chooses.
- Unship does not replace design review, browser QA, accessibility checks, or production release validation.
- Unship is not intended for production traffic, remote analytics, or persistent product experiments.
## Security & Safety Notes
- Run commands only in a local project the user has authorized you to modify.
- Treat generated variants as temporary code that must be cleaned before release.
- Before destructive cleanup, confirm the selected option label when the user's choice is ambiguous.
- If a baseline build or typecheck already fails before Unship edits, report that baseline state and keep variant work isolated.
## Common Pitfalls
- **Problem:** Hidden variants override `hidden` with CSS.
**Solution:** Preserve `[hidden] { display: none !important; }` near variant-specific CSS when needed.
- **Problem:** The user says "keep the second one" after more changes.
**Solution:** Confirm the exact group and option label before editing source.
- **Problem:** The comparison grows into a broad redesign.
**Solution:** Reduce scope to the smallest section, state, or flow that can be judged in the running app.
## Related Skills
- `@webapp-testing` - Use for browser-based functional checks after frontend changes.
- `@mobile-design` - Use when comparing mobile-specific UI patterns and platform constraints.

View File

@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-agent-mcp-builder",
"version": "12.2.1",
"version": "12.3.0",
"description": "Editorial \"AAS Agent & MCP Builder\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "agyb-aas-agent-mcp-builder",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Agent & MCP Builder\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-automation-builder",
"version": "12.2.1",
"version": "12.3.0",
"description": "Editorial \"AAS Automation Builder\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "agyb-aas-automation-builder",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Automation Builder\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-data-analytics",
"version": "12.2.1",
"version": "12.3.0",
"description": "Editorial \"AAS Data Analytics\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "agyb-aas-data-analytics",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Data Analytics\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-devops-cloud",
"version": "12.2.1",
"version": "12.3.0",
"description": "Editorial \"AAS DevOps & Cloud\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "agyb-aas-devops-cloud",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS DevOps & Cloud\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-documents-presentations",
"version": "12.2.1",
"version": "12.3.0",
"description": "Editorial \"AAS Documents & Presentations\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "agyb-aas-documents-presentations",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Documents & Presentations\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-marketing-seo-growth",
"version": "12.2.1",
"version": "12.3.0",
"description": "Editorial \"AAS Marketing, SEO & Growth\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "agyb-aas-marketing-seo-growth",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Marketing, SEO & Growth\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-mobile-app-builder",
"version": "12.2.1",
"version": "12.3.0",
"description": "Editorial \"AAS Mobile App Builder\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "agyb-aas-mobile-app-builder",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Mobile App Builder\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-observability-ir",
"version": "12.2.1",
"version": "12.3.0",
"description": "Editorial \"AAS Observability IR\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "agyb-aas-observability-ir",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Observability IR\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-oss-maintainer",
"version": "12.2.1",
"version": "12.3.0",
"description": "Editorial \"AAS OSS Maintainer\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "agyb-aas-oss-maintainer",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS OSS Maintainer\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-product-design-studio",
"version": "12.2.1",
"version": "12.3.0",
"description": "Editorial \"AAS Product Design Studio\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "agyb-aas-product-design-studio",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Product Design Studio\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-python-api-builder",
"version": "12.2.1",
"version": "12.3.0",
"description": "Editorial \"AAS Python API Builder\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "agyb-aas-python-api-builder",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Python API Builder\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-qa-test-automation",
"version": "12.2.1",
"version": "12.3.0",
"description": "Editorial \"AAS QA & Test Automation\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "agyb-aas-qa-test-automation",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS QA & Test Automation\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-secure-app-builder",
"version": "12.2.1",
"version": "12.3.0",
"description": "Editorial \"AAS Secure App Builder\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "agyb-aas-secure-app-builder",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Secure App Builder\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-security-engineer",
"version": "12.2.1",
"version": "12.3.0",
"description": "Editorial \"AAS Security Engineer\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "agyb-aas-security-engineer",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Security Engineer\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-aas-web-app-builder",
"version": "12.2.1",
"version": "12.3.0",
"description": "Editorial \"AAS Web App Builder\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "agyb-aas-web-app-builder",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"AAS Web App Builder\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-agent-architect",
"version": "12.2.1",
"version": "12.3.0",
"description": "Editorial \"Agent Architect\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "agyb-agent-architect",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Agent Architect\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-apple-platform-design",
"version": "12.2.1",
"version": "12.3.0",
"description": "Editorial \"Apple Platform Design\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "agyb-apple-platform-design",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Apple Platform Design\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-architecture-design",
"version": "12.2.1",
"version": "12.3.0",
"description": "Editorial \"Architecture & Design\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "agyb-architecture-design",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Architecture & Design\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-automation-builder",
"version": "12.2.1",
"version": "12.3.0",
"description": "Editorial \"Automation Builder\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "agyb-automation-builder",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Automation Builder\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-azure-ai-cloud",
"version": "12.2.1",
"version": "12.3.0",
"description": "Editorial \"Azure AI & Cloud\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "agyb-azure-ai-cloud",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Azure AI & Cloud\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-business-analyst",
"version": "12.2.1",
"version": "12.3.0",
"description": "Editorial \"Business Analyst\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "agyb-business-analyst",
"version": "12.2.1",
"version": "12.3.0",
"description": "Install the \"Business Analyst\" editorial skill bundle from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

View File

@ -1,6 +1,6 @@
{
"name": "antigravity-bundle-commerce-payments",
"version": "12.2.1",
"version": "12.3.0",
"description": "Editorial \"Commerce & Payments\" bundle for Claude Code from Antigravity Awesome Skills.",
"author": {
"name": "sickn33 and contributors",

Some files were not shown because too many files have changed in this diff Show More