cfsa-antigravity 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.mjs +324 -0
- package/package.json +34 -0
- package/template/.agent/instructions/commands.md +48 -0
- package/template/.agent/instructions/patterns.md +61 -0
- package/template/.agent/instructions/structure.md +29 -0
- package/template/.agent/instructions/tech-stack.md +43 -0
- package/template/.agent/instructions/workflow.md +41 -0
- package/template/.agent/kit-sync.md +15 -0
- package/template/.agent/rules/boundary-not-placeholder.md +146 -0
- package/template/.agent/rules/completion-checklist.md +48 -0
- package/template/.agent/rules/decision-classification.md +103 -0
- package/template/.agent/rules/extensibility.md +47 -0
- package/template/.agent/rules/question-vs-command.md +81 -0
- package/template/.agent/rules/security-first.md +43 -0
- package/template/.agent/rules/specificity-standards.md +54 -0
- package/template/.agent/rules/tdd-contract-first.md +57 -0
- package/template/.agent/rules/vertical-slices.md +42 -0
- package/template/.agent/skill-library/MANIFEST.md +480 -0
- package/template/.agent/skill-library/README.md +38 -0
- package/template/.agent/skill-library/meta/brand-guidelines/SKILL.md +73 -0
- package/template/.agent/skill-library/meta/claude-code/README.md +9 -0
- package/template/.agent/skill-library/meta/claude-code/agent-development/SKILL.md +415 -0
- package/template/.agent/skill-library/meta/claude-code/hook-development/SKILL.md +712 -0
- package/template/.agent/skill-library/meta/claude-code/plugin-structure/SKILL.md +476 -0
- package/template/.agent/skill-library/meta/git-advanced/SKILL.md +972 -0
- package/template/.agent/skill-library/meta/mcp-builder/SKILL.md +236 -0
- package/template/.agent/skill-library/meta/product-marketing-context/SKILL.md +241 -0
- package/template/.agent/skill-library/meta/regex-patterns/SKILL.md +751 -0
- package/template/.agent/skill-library/meta/tmux-processes/SKILL.md +210 -0
- package/template/.agent/skill-library/meta/using-tmux-for-interactive-commands/SKILL.md +178 -0
- package/template/.agent/skill-library/stack/3d/threejs-pro/SKILL.md +300 -0
- package/template/.agent/skill-library/stack/ai/ai-sdk/SKILL.md +77 -0
- package/template/.agent/skill-library/stack/ai/langchain/SKILL.md +530 -0
- package/template/.agent/skill-library/stack/ai/ollama/SKILL.md +321 -0
- package/template/.agent/skill-library/stack/ai/openai-sdk/SKILL.md +549 -0
- package/template/.agent/skill-library/stack/analytics/google-analytics/SKILL.md +153 -0
- package/template/.agent/skill-library/stack/api/graphql/SKILL.md +1061 -0
- package/template/.agent/skill-library/stack/api/trpc/SKILL.md +576 -0
- package/template/.agent/skill-library/stack/auth/authjs/SKILL.md +569 -0
- package/template/.agent/skill-library/stack/auth/clerk/SKILL.md +590 -0
- package/template/.agent/skill-library/stack/auth/firebase-auth/SKILL.md +734 -0
- package/template/.agent/skill-library/stack/cms/payload-cms/SKILL.md +573 -0
- package/template/.agent/skill-library/stack/cms/shopify/SKILL.md +1193 -0
- package/template/.agent/skill-library/stack/cms/wordpress/SKILL.md +1104 -0
- package/template/.agent/skill-library/stack/css/sass-scss/SKILL.md +1121 -0
- package/template/.agent/skill-library/stack/css/tailwind-css-patterns/SKILL.md +863 -0
- package/template/.agent/skill-library/stack/css/tailwind-design-system/SKILL.md +490 -0
- package/template/.agent/skill-library/stack/css/vanilla-css/SKILL.md +1078 -0
- package/template/.agent/skill-library/stack/databases/clickhouse/SKILL.md +311 -0
- package/template/.agent/skill-library/stack/databases/influxdb/SKILL.md +280 -0
- package/template/.agent/skill-library/stack/databases/lancedb/SKILL.md +415 -0
- package/template/.agent/skill-library/stack/databases/mongodb/SKILL.md +1169 -0
- package/template/.agent/skill-library/stack/databases/neo4j/SKILL.md +839 -0
- package/template/.agent/skill-library/stack/databases/pgvector/SKILL.md +241 -0
- package/template/.agent/skill-library/stack/databases/pinecone/SKILL.md +212 -0
- package/template/.agent/skill-library/stack/databases/postgresql/SKILL.md +658 -0
- package/template/.agent/skill-library/stack/databases/qdrant/SKILL.md +312 -0
- package/template/.agent/skill-library/stack/databases/redis/SKILL.md +1079 -0
- package/template/.agent/skill-library/stack/databases/spacetimedb/SKILL.md +532 -0
- package/template/.agent/skill-library/stack/databases/sqlite/SKILL.md +1132 -0
- package/template/.agent/skill-library/stack/databases/supabase/SKILL.md +640 -0
- package/template/.agent/skill-library/stack/databases/surrealdb-expert/SKILL.md +945 -0
- package/template/.agent/skill-library/stack/databases/timescaledb/SKILL.md +745 -0
- package/template/.agent/skill-library/stack/databases/weaviate/SKILL.md +218 -0
- package/template/.agent/skill-library/stack/devops/github-actions/SKILL.md +554 -0
- package/template/.agent/skill-library/stack/devops/kubernetes/SKILL.md +950 -0
- package/template/.agent/skill-library/stack/devops/nginx/SKILL.md +841 -0
- package/template/.agent/skill-library/stack/devops/terraform/SKILL.md +860 -0
- package/template/.agent/skill-library/stack/email/resend/SKILL.md +391 -0
- package/template/.agent/skill-library/stack/engines/godot/SKILL.md +488 -0
- package/template/.agent/skill-library/stack/extensions/chrome-extension/SKILL.md +375 -0
- package/template/.agent/skill-library/stack/extensions/vscode-extension/SKILL.md +453 -0
- package/template/.agent/skill-library/stack/frameworks/astro-framework/SKILL.md +162 -0
- package/template/.agent/skill-library/stack/frameworks/electron/SKILL.md +1286 -0
- package/template/.agent/skill-library/stack/frameworks/fastapi/SKILL.md +650 -0
- package/template/.agent/skill-library/stack/frameworks/hono/SKILL.md +90 -0
- package/template/.agent/skill-library/stack/frameworks/nestjs/SKILL.md +878 -0
- package/template/.agent/skill-library/stack/frameworks/nextjs/SKILL.md +635 -0
- package/template/.agent/skill-library/stack/frameworks/nuxt/SKILL.md +564 -0
- package/template/.agent/skill-library/stack/frameworks/sveltekit/SKILL.md +614 -0
- package/template/.agent/skill-library/stack/frameworks/tauri/SKILL.md +920 -0
- package/template/.agent/skill-library/stack/gamedev/godot/SKILL.md +1032 -0
- package/template/.agent/skill-library/stack/gamedev/unity/SKILL.md +1175 -0
- package/template/.agent/skill-library/stack/hosting/aws/SKILL.md +467 -0
- package/template/.agent/skill-library/stack/hosting/cloudflare/SKILL.md +201 -0
- package/template/.agent/skill-library/stack/hosting/docker-expert/SKILL.md +409 -0
- package/template/.agent/skill-library/stack/hosting/vercel/SKILL.md +484 -0
- package/template/.agent/skill-library/stack/languages/bash-scripting/SKILL.md +773 -0
- package/template/.agent/skill-library/stack/languages/c-cpp/SKILL.md +712 -0
- package/template/.agent/skill-library/stack/languages/gdscript/SKILL.md +789 -0
- package/template/.agent/skill-library/stack/languages/go/SKILL.md +664 -0
- package/template/.agent/skill-library/stack/languages/java/SKILL.md +778 -0
- package/template/.agent/skill-library/stack/languages/kotlin/SKILL.md +665 -0
- package/template/.agent/skill-library/stack/languages/python/SKILL.md +678 -0
- package/template/.agent/skill-library/stack/languages/rust/SKILL.md +673 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/SKILL.md +141 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/advanced-generics.md +90 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/branded-types.md +57 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/builder-pattern.md +71 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/common-pitfalls.md +135 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/conditional-types.md +27 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/decorators.md +98 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/discriminated-unions.md +62 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/mapped-types.md +53 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/performance-best-practices.md +104 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/template-literal-types.md +49 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/testing-types.md +112 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/type-guards.md +70 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/type-inference.md +101 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/utility-types.md +98 -0
- package/template/.agent/skill-library/stack/languages/vanilla-javascript/SKILL.md +803 -0
- package/template/.agent/skill-library/stack/messaging/kafka/SKILL.md +235 -0
- package/template/.agent/skill-library/stack/mobile/expo-react-native/SKILL.md +665 -0
- package/template/.agent/skill-library/stack/mobile/flutter/SKILL.md +316 -0
- package/template/.agent/skill-library/stack/mobile/react-native/SKILL.md +337 -0
- package/template/.agent/skill-library/stack/monitoring/posthog/SKILL.md +396 -0
- package/template/.agent/skill-library/stack/monitoring/sentry/SKILL.md +509 -0
- package/template/.agent/skill-library/stack/observability/datadog/SKILL.md +179 -0
- package/template/.agent/skill-library/stack/observability/distributed-tracing/SKILL.md +140 -0
- package/template/.agent/skill-library/stack/observability/logging-best-practices/SKILL.md +168 -0
- package/template/.agent/skill-library/stack/observability/opentelemetry/SKILL.md +164 -0
- package/template/.agent/skill-library/stack/observability/prometheus-grafana/SKILL.md +246 -0
- package/template/.agent/skill-library/stack/observability/python-observability/SKILL.md +158 -0
- package/template/.agent/skill-library/stack/orm/drizzle-orm/SKILL.md +613 -0
- package/template/.agent/skill-library/stack/orm/prisma/SKILL.md +744 -0
- package/template/.agent/skill-library/stack/payments/lemonsqueezy/SKILL.md +393 -0
- package/template/.agent/skill-library/stack/payments/stripe-integration/SKILL.md +457 -0
- package/template/.agent/skill-library/stack/queue/bullmq/SKILL.md +385 -0
- package/template/.agent/skill-library/stack/queue/inngest/SKILL.md +438 -0
- package/template/.agent/skill-library/stack/realtime/socketio/SKILL.md +595 -0
- package/template/.agent/skill-library/stack/search/elasticsearch/SKILL.md +248 -0
- package/template/.agent/skill-library/stack/search/meilisearch/SKILL.md +385 -0
- package/template/.agent/skill-library/stack/security/crypto-patterns/SKILL.md +437 -0
- package/template/.agent/skill-library/stack/security/csp-cors-headers/SKILL.md +588 -0
- package/template/.agent/skill-library/stack/security/dependency-auditing/SKILL.md +560 -0
- package/template/.agent/skill-library/stack/security/input-sanitization/SKILL.md +430 -0
- package/template/.agent/skill-library/stack/security/owasp-web-security/SKILL.md +421 -0
- package/template/.agent/skill-library/stack/state/tanstack-query/SKILL.md +637 -0
- package/template/.agent/skill-library/stack/state/zustand/SKILL.md +483 -0
- package/template/.agent/skill-library/stack/storage/aws-s3/SKILL.md +415 -0
- package/template/.agent/skill-library/stack/testing/playwright/SKILL.md +641 -0
- package/template/.agent/skill-library/stack/testing/storybook/SKILL.md +923 -0
- package/template/.agent/skill-library/stack/testing/testing-library/SKILL.md +872 -0
- package/template/.agent/skill-library/stack/testing/vitest/SKILL.md +714 -0
- package/template/.agent/skill-library/stack/ui/react-best-practices/SKILL.md +877 -0
- package/template/.agent/skill-library/stack/ui/react-composition-patterns/SKILL.md +1107 -0
- package/template/.agent/skill-library/stack/ui/react-flow/SKILL.md +425 -0
- package/template/.agent/skill-library/stack/ui/shadcn-ui/SKILL.md +703 -0
- package/template/.agent/skill-library/surface/api/api-caching/SKILL.md +458 -0
- package/template/.agent/skill-library/surface/api/api-documentation-openapi/SKILL.md +697 -0
- package/template/.agent/skill-library/surface/api/api-error-handling/SKILL.md +478 -0
- package/template/.agent/skill-library/surface/api/api-security-checklist/SKILL.md +147 -0
- package/template/.agent/skill-library/surface/api/api-versioning/SKILL.md +420 -0
- package/template/.agent/skill-library/surface/api/email-best-practices/SKILL.md +59 -0
- package/template/.agent/skill-library/surface/api/rate-limiting-abuse-protection/SKILL.md +147 -0
- package/template/.agent/skill-library/surface/api/rest-api-design/SKILL.md +478 -0
- package/template/.agent/skill-library/surface/api/webhook-design/SKILL.md +752 -0
- package/template/.agent/skill-library/surface/cli/cli-configuration-management/SKILL.md +445 -0
- package/template/.agent/skill-library/surface/cli/cli-error-diagnostics/SKILL.md +515 -0
- package/template/.agent/skill-library/surface/cli/cli-shell-integration/SKILL.md +479 -0
- package/template/.agent/skill-library/surface/cli/cli-ux-design/SKILL.md +477 -0
- package/template/.agent/skill-library/surface/desktop/desktop-app-distribution/SKILL.md +416 -0
- package/template/.agent/skill-library/surface/desktop/desktop-security-sandboxing/SKILL.md +407 -0
- package/template/.agent/skill-library/surface/desktop/desktop-ux-conventions/SKILL.md +361 -0
- package/template/.agent/skill-library/surface/desktop/native-os-integration/SKILL.md +563 -0
- package/template/.agent/skill-library/surface/extension/browser-extension-patterns/SKILL.md +482 -0
- package/template/.agent/skill-library/surface/extension/plugin-architecture-design/SKILL.md +632 -0
- package/template/.agent/skill-library/surface/extension/vscode-extension-development/SKILL.md +728 -0
- package/template/.agent/skill-library/surface/mobile/app-store-submission/SKILL.md +304 -0
- package/template/.agent/skill-library/surface/mobile/mobile-offline-sync/SKILL.md +443 -0
- package/template/.agent/skill-library/surface/mobile/mobile-responsive-patterns/SKILL.md +432 -0
- package/template/.agent/skill-library/surface/mobile/push-notifications/SKILL.md +495 -0
- package/template/.agent/skill-library/surface/web/accessibility-compliance/SKILL.md +827 -0
- package/template/.agent/skill-library/surface/web/ai-seo/SKILL.md +398 -0
- package/template/.agent/skill-library/surface/web/ai-seo/references/content-patterns.md +285 -0
- package/template/.agent/skill-library/surface/web/ai-seo/references/platform-ranking-factors.md +152 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/SKILL.md +309 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/references/event-library.md +260 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/references/ga4-implementation.md +300 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/references/gtm-implementation.md +390 -0
- package/template/.agent/skill-library/surface/web/authentication-ui-flows/SKILL.md +530 -0
- package/template/.agent/skill-library/surface/web/dark-mode-theming/SKILL.md +516 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/SKILL.md +105 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/charts.csv +26 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/colors.csv +97 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/landing.csv +31 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/styles.csv +59 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/typography.csv +58 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/ux-guidelines.csv +100 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/scripts/core.py +258 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/scripts/design_system.py +1067 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/scripts/search.py +106 -0
- package/template/.agent/skill-library/surface/web/form-handling-validation/SKILL.md +675 -0
- package/template/.agent/skill-library/surface/web/frontend-design/SKILL.md +1393 -0
- package/template/.agent/skill-library/surface/web/frontend-design/templates/cppn-hero.tsx +299 -0
- package/template/.agent/skill-library/surface/web/frontend-design/templates/wave-hero.tsx +875 -0
- package/template/.agent/skill-library/surface/web/frontend-verification/SKILL.md +111 -0
- package/template/.agent/skill-library/surface/web/frontend-verification/scripts/ux_audit.py +739 -0
- package/template/.agent/skill-library/surface/web/i18n-localization/SKILL.md +154 -0
- package/template/.agent/skill-library/surface/web/offline-first-pwa/SKILL.md +657 -0
- package/template/.agent/skill-library/surface/web/page-cro/SKILL.md +182 -0
- package/template/.agent/skill-library/surface/web/page-cro/references/experiments.md +248 -0
- package/template/.agent/skill-library/surface/web/programmatic-seo/SKILL.md +238 -0
- package/template/.agent/skill-library/surface/web/programmatic-seo/references/playbooks.md +308 -0
- package/template/.agent/skill-library/surface/web/schema-markup/SKILL.md +179 -0
- package/template/.agent/skill-library/surface/web/schema-markup/references/schema-examples.md +398 -0
- package/template/.agent/skill-library/surface/web/seo-audit/SKILL.md +394 -0
- package/template/.agent/skill-library/surface/web/seo-audit/references/ai-writing-detection.md +200 -0
- package/template/.agent/skill-library/surface/web/web-performance-optimization/SKILL.md +646 -0
- package/template/.agent/skill-library/surface/web/web-scraping/SKILL.md +58 -0
- package/template/.agent/skills/accessibility/SKILL.md +522 -0
- package/template/.agent/skills/accessibility/references/WCAG.md +162 -0
- package/template/.agent/skills/adversarial-review/SKILL.md +90 -0
- package/template/.agent/skills/antigravity-workflows/SKILL.md +81 -0
- package/template/.agent/skills/antigravity-workflows/resources/implementation-playbook.md +36 -0
- package/template/.agent/skills/api-design-principles/SKILL.md +37 -0
- package/template/.agent/skills/api-design-principles/assets/api-design-checklist.md +155 -0
- package/template/.agent/skills/api-design-principles/assets/rest-api-template.py +182 -0
- package/template/.agent/skills/api-design-principles/references/graphql-schema-design.md +583 -0
- package/template/.agent/skills/api-design-principles/references/rest-best-practices.md +408 -0
- package/template/.agent/skills/api-design-principles/resources/implementation-playbook.md +513 -0
- package/template/.agent/skills/api-versioning/SKILL.md +420 -0
- package/template/.agent/skills/architecture-mapping/SKILL.md +219 -0
- package/template/.agent/skills/bootstrap-agents/SKILL.md +259 -0
- package/template/.agent/skills/brainstorming/SKILL.md +236 -0
- package/template/.agent/skills/brand-guidelines/SKILL.md +44 -0
- package/template/.agent/skills/clean-code/SKILL.md +94 -0
- package/template/.agent/skills/code-review-pro/SKILL.md +152 -0
- package/template/.agent/skills/concise-planning/SKILL.md +68 -0
- package/template/.agent/skills/cross-layer-consistency/SKILL.md +117 -0
- package/template/.agent/skills/database-schema-design/SKILL.md +429 -0
- package/template/.agent/skills/deployment-procedures/SKILL.md +241 -0
- package/template/.agent/skills/design-anti-cliche/SKILL.md +159 -0
- package/template/.agent/skills/design-direction/SKILL.md +45 -0
- package/template/.agent/skills/error-handling-patterns/SKILL.md +721 -0
- package/template/.agent/skills/find-skills/SKILL.md +145 -0
- package/template/.agent/skills/git-advanced/SKILL.md +972 -0
- package/template/.agent/skills/git-workflow/SKILL.md +420 -0
- package/template/.agent/skills/idea-extraction/SKILL.md +271 -0
- package/template/.agent/skills/logging-best-practices/SKILL.md +851 -0
- package/template/.agent/skills/migration-management/SKILL.md +384 -0
- package/template/.agent/skills/minimalist-surgical-development/SKILL.md +69 -0
- package/template/.agent/skills/parallel-agents/SKILL.md +165 -0
- package/template/.agent/skills/parallel-debugging/SKILL.md +135 -0
- package/template/.agent/skills/parallel-feature-development/SKILL.md +166 -0
- package/template/.agent/skills/performance-budgeting/SKILL.md +144 -0
- package/template/.agent/skills/pipeline-rubrics/SKILL.md +51 -0
- package/template/.agent/skills/pipeline-rubrics/references/architecture-rubric.md +19 -0
- package/template/.agent/skills/pipeline-rubrics/references/be-rubric.md +21 -0
- package/template/.agent/skills/pipeline-rubrics/references/fe-rubric.md +20 -0
- package/template/.agent/skills/pipeline-rubrics/references/ia-rubric.md +19 -0
- package/template/.agent/skills/pipeline-rubrics/references/scoring.md +28 -0
- package/template/.agent/skills/pipeline-rubrics/references/vision-rubric.md +11 -0
- package/template/.agent/skills/prd-templates/SKILL.md +88 -0
- package/template/.agent/skills/prd-templates/references/architecture-design-template.md +88 -0
- package/template/.agent/skills/prd-templates/references/be-spec-template.md +101 -0
- package/template/.agent/skills/prd-templates/references/data-placement-template.md +74 -0
- package/template/.agent/skills/prd-templates/references/decomposition-templates.md +211 -0
- package/template/.agent/skills/prd-templates/references/design-system-decisions.md +198 -0
- package/template/.agent/skills/prd-templates/references/engineering-standards-template.md +124 -0
- package/template/.agent/skills/prd-templates/references/fe-classification-procedures.md +47 -0
- package/template/.agent/skills/prd-templates/references/fe-spec-template.md +84 -0
- package/template/.agent/skills/prd-templates/references/infrastructure-report-template.md +71 -0
- package/template/.agent/skills/prd-templates/references/operational-templates.md +116 -0
- package/template/.agent/skills/prd-templates/references/placeholder-guard-template.md +21 -0
- package/template/.agent/skills/prd-templates/references/surface-model.md +61 -0
- package/template/.agent/skills/prd-templates/references/vision-template.md +66 -0
- package/template/.agent/skills/prompt-engineer/README.md +659 -0
- package/template/.agent/skills/prompt-engineer/SKILL.md +249 -0
- package/template/.agent/skills/regex-patterns/SKILL.md +751 -0
- package/template/.agent/skills/resolve-ambiguity/SKILL.md +278 -0
- package/template/.agent/skills/rest-api-design/SKILL.md +478 -0
- package/template/.agent/skills/security-scanning-security-hardening/SKILL.md +231 -0
- package/template/.agent/skills/session-continuity/SKILL.md +730 -0
- package/template/.agent/skills/session-continuity/protocols/01-session-resumption.md +38 -0
- package/template/.agent/skills/session-continuity/protocols/02-progress-generation.md +85 -0
- package/template/.agent/skills/session-continuity/protocols/03-progress-update.md +70 -0
- package/template/.agent/skills/session-continuity/protocols/04-pattern-extraction.md +60 -0
- package/template/.agent/skills/session-continuity/protocols/05-session-close.md +37 -0
- package/template/.agent/skills/session-continuity/protocols/06-decision-analysis.md +84 -0
- package/template/.agent/skills/session-continuity/protocols/07-spec-pipeline-generation.md +48 -0
- package/template/.agent/skills/session-continuity/protocols/08-spec-pipeline-update.md +43 -0
- package/template/.agent/skills/session-continuity/protocols/09-parallel-claim.md +122 -0
- package/template/.agent/skills/session-continuity/protocols/10-placeholder-verification-gate.md +104 -0
- package/template/.agent/skills/session-continuity/protocols/ambiguity-gates.md +48 -0
- package/template/.agent/skills/skill-creator/LICENSE.txt +202 -0
- package/template/.agent/skills/skill-creator/README.md +270 -0
- package/template/.agent/skills/skill-creator/SKILL.md +590 -0
- package/template/.agent/skills/skill-creator/references/output-patterns.md +82 -0
- package/template/.agent/skills/skill-creator/references/workflows.md +28 -0
- package/template/.agent/skills/skill-creator/scripts/init_skill.py +303 -0
- package/template/.agent/skills/skill-creator/scripts/package_skill.py +110 -0
- package/template/.agent/skills/skill-creator/scripts/quick_validate.py +95 -0
- package/template/.agent/skills/spec-writing/SKILL.md +110 -0
- package/template/.agent/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/template/.agent/skills/systematic-debugging/SKILL.md +297 -0
- package/template/.agent/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/template/.agent/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/template/.agent/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/template/.agent/skills/systematic-debugging/find-polluter.sh +63 -0
- package/template/.agent/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/template/.agent/skills/systematic-debugging/test-academic.md +14 -0
- package/template/.agent/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/template/.agent/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/template/.agent/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/template/.agent/skills/tdd-workflow/SKILL.md +409 -0
- package/template/.agent/skills/tech-stack-catalog/SKILL.md +49 -0
- package/template/.agent/skills/tech-stack-catalog/references/constraint-questions.md +21 -0
- package/template/.agent/skills/tech-stack-catalog/references/dev-tooling-decisions.md +37 -0
- package/template/.agent/skills/tech-stack-catalog/references/surface-decision-tables.md +69 -0
- package/template/.agent/skills/technical-writer/SKILL.md +242 -0
- package/template/.agent/skills/testing-strategist/SKILL.md +932 -0
- package/template/.agent/skills/verification-before-completion/SKILL.md +145 -0
- package/template/.agent/skills/workflow-automation/SKILL.md +73 -0
- package/template/.agent/workflows/audit-ambiguity-execute.md +165 -0
- package/template/.agent/workflows/audit-ambiguity-rubrics.md +83 -0
- package/template/.agent/workflows/audit-ambiguity.md +64 -0
- package/template/.agent/workflows/bootstrap-agents-fill.md +201 -0
- package/template/.agent/workflows/bootstrap-agents-provision.md +197 -0
- package/template/.agent/workflows/bootstrap-agents.md +66 -0
- package/template/.agent/workflows/create-prd-architecture.md +119 -0
- package/template/.agent/workflows/create-prd-compile.md +138 -0
- package/template/.agent/workflows/create-prd-design-system.md +135 -0
- package/template/.agent/workflows/create-prd-security.md +113 -0
- package/template/.agent/workflows/create-prd-stack.md +91 -0
- package/template/.agent/workflows/create-prd.md +168 -0
- package/template/.agent/workflows/decompose-architecture-structure.md +82 -0
- package/template/.agent/workflows/decompose-architecture-validate.md +119 -0
- package/template/.agent/workflows/decompose-architecture.md +111 -0
- package/template/.agent/workflows/evolve-contract.md +98 -0
- package/template/.agent/workflows/evolve-feature-cascade.md +140 -0
- package/template/.agent/workflows/evolve-feature-classify.md +116 -0
- package/template/.agent/workflows/evolve-feature.md +56 -0
- package/template/.agent/workflows/ideate-discover.md +144 -0
- package/template/.agent/workflows/ideate-extract.md +129 -0
- package/template/.agent/workflows/ideate-validate.md +117 -0
- package/template/.agent/workflows/ideate.md +113 -0
- package/template/.agent/workflows/implement-slice-setup.md +113 -0
- package/template/.agent/workflows/implement-slice-tdd.md +198 -0
- package/template/.agent/workflows/implement-slice.md +50 -0
- package/template/.agent/workflows/plan-phase.md +202 -0
- package/template/.agent/workflows/propagate-decision-apply.md +135 -0
- package/template/.agent/workflows/propagate-decision-scan.md +147 -0
- package/template/.agent/workflows/propagate-decision.md +56 -0
- package/template/.agent/workflows/remediate-pipeline-assess.md +138 -0
- package/template/.agent/workflows/remediate-pipeline-execute.md +135 -0
- package/template/.agent/workflows/remediate-pipeline.md +55 -0
- package/template/.agent/workflows/resolve-ambiguity.md +82 -0
- package/template/.agent/workflows/sync-kit.md +209 -0
- package/template/.agent/workflows/update-architecture-map.md +74 -0
- package/template/.agent/workflows/validate-phase.md +219 -0
- package/template/.agent/workflows/verify-infrastructure.md +207 -0
- package/template/.agent/workflows/write-architecture-spec-deepen.md +139 -0
- package/template/.agent/workflows/write-architecture-spec-design.md +202 -0
- package/template/.agent/workflows/write-architecture-spec.md +63 -0
- package/template/.agent/workflows/write-be-spec-classify.md +165 -0
- package/template/.agent/workflows/write-be-spec-write.md +98 -0
- package/template/.agent/workflows/write-be-spec.md +76 -0
- package/template/.agent/workflows/write-fe-spec-classify.md +170 -0
- package/template/.agent/workflows/write-fe-spec-write.md +94 -0
- package/template/.agent/workflows/write-fe-spec.md +71 -0
- package/template/AGENTS.md +176 -0
- package/template/GEMINI.md +177 -0
- package/template/docs/README.md +187 -0
- package/template/docs/audits/.gitkeep +0 -0
- package/template/docs/audits/README.md +10 -0
- package/template/docs/plans/.gitkeep +0 -0
- package/template/docs/plans/README.md +21 -0
- package/template/docs/plans/be/.gitkeep +0 -0
- package/template/docs/plans/be/README.md +11 -0
- package/template/docs/plans/fe/.gitkeep +0 -0
- package/template/docs/plans/fe/README.md +11 -0
- package/template/docs/plans/ia/.gitkeep +0 -0
- package/template/docs/plans/ia/README.md +17 -0
- package/template/docs/plans/ia/deep-dives/.gitkeep +0 -0
- package/template/docs/plans/ia/deep-dives/README.md +5 -0
- package/template/docs/plans/phases/.gitkeep +0 -0
- package/template/docs/plans/phases/README.md +11 -0
|
@@ -0,0 +1,1286 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: electron
|
|
3
|
+
description: Provides comprehensive Electron desktop application development patterns including main/renderer process architecture, IPC communication (ipcMain, ipcRenderer, contextBridge), preload scripts, security best practices, BrowserWindow management, auto-updater, native menus, system tray, file dialogs, protocol handlers, Electron Forge and electron-builder, performance optimization, memory management, crash reporting, and testing strategies. Use when building cross-platform desktop apps with Electron.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
allowed-tools: Read, Write, Edit, Glob, Grep, Bash
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Electron Desktop Application Development Patterns
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
Expert guide for building cross-platform desktop applications with Electron. Covers the multi-process architecture, secure IPC communication, window management, native OS integration, packaging, performance optimization, and testing. Targets Electron 30+ with modern security defaults.
|
|
13
|
+
|
|
14
|
+
## When to Use
|
|
15
|
+
|
|
16
|
+
- Building cross-platform desktop applications (Windows, macOS, Linux)
|
|
17
|
+
- When you need full Node.js access for backend operations
|
|
18
|
+
- When Chromium rendering consistency across platforms matters
|
|
19
|
+
- When the team has strong JavaScript/TypeScript expertise
|
|
20
|
+
- Building apps that embed web content or wrap existing web apps
|
|
21
|
+
- When rich ecosystem of npm packages is an advantage
|
|
22
|
+
- Building IDEs, communication apps, media tools, or developer utilities
|
|
23
|
+
|
|
24
|
+
## Instructions
|
|
25
|
+
|
|
26
|
+
1. **Enable all security defaults**: contextIsolation, sandbox, no nodeIntegration
|
|
27
|
+
2. **Use preload scripts** for all main-to-renderer communication
|
|
28
|
+
3. **Validate all IPC messages** on both sides
|
|
29
|
+
4. **Use contextBridge** to expose a minimal, typed API
|
|
30
|
+
5. **Never load remote content** without strict CSP and webview sandboxing
|
|
31
|
+
6. **Manage window lifecycle** explicitly (remember bounds, handle close vs quit)
|
|
32
|
+
7. **Profile memory** regularly -- Electron apps are prone to leaks
|
|
33
|
+
|
|
34
|
+
## Examples
|
|
35
|
+
|
|
36
|
+
### Project Structure
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
my-electron-app/
|
|
40
|
+
src/
|
|
41
|
+
main/
|
|
42
|
+
index.ts # Main process entry
|
|
43
|
+
ipc-handlers.ts # IPC handler registration
|
|
44
|
+
menu.ts # Application menu
|
|
45
|
+
tray.ts # System tray
|
|
46
|
+
updater.ts # Auto-update logic
|
|
47
|
+
windows/
|
|
48
|
+
main-window.ts # Main window factory
|
|
49
|
+
settings-window.ts
|
|
50
|
+
preload/
|
|
51
|
+
index.ts # Preload script
|
|
52
|
+
api.ts # contextBridge API definition
|
|
53
|
+
renderer/
|
|
54
|
+
index.html # Entry HTML
|
|
55
|
+
App.tsx # React/Vue/Svelte app
|
|
56
|
+
lib/
|
|
57
|
+
electron-api.ts # Typed wrapper for exposed API
|
|
58
|
+
electron-builder.yml # Build configuration
|
|
59
|
+
forge.config.ts # Electron Forge config (alternative)
|
|
60
|
+
package.json
|
|
61
|
+
tsconfig.json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Constraints and Warnings
|
|
65
|
+
|
|
66
|
+
- **Bundle size**: Electron ships Chromium + Node.js (~150MB minimum)
|
|
67
|
+
- **Memory usage**: Each window is a separate Chromium process (50-100MB+)
|
|
68
|
+
- **Startup time**: Cold start can be 2-5 seconds depending on app complexity
|
|
69
|
+
- **Security surface**: Node.js in main process has full system access
|
|
70
|
+
- **Update complexity**: Auto-updates require code signing on macOS and Windows
|
|
71
|
+
- **macOS notarization**: Required for distribution outside the App Store
|
|
72
|
+
- **Native modules**: Must be rebuilt for Electron's Node.js version
|
|
73
|
+
- **Chromium version**: Locked to Electron's bundled version, not the user's browser
|
|
74
|
+
|
|
75
|
+
## Core Concepts
|
|
76
|
+
|
|
77
|
+
### Process Architecture
|
|
78
|
+
|
|
79
|
+
Electron has two types of processes:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
Main Process (Node.js)
|
|
83
|
+
- Runs in Node.js environment
|
|
84
|
+
- Has full system access (file system, network, OS APIs)
|
|
85
|
+
- Creates and manages BrowserWindows
|
|
86
|
+
- Handles IPC from renderer processes
|
|
87
|
+
- Manages app lifecycle, menus, tray, dialogs
|
|
88
|
+
|
|
89
|
+
Renderer Process (Chromium)
|
|
90
|
+
- Runs in a Chromium browser context
|
|
91
|
+
- One process per BrowserWindow
|
|
92
|
+
- No direct Node.js access (when properly configured)
|
|
93
|
+
- Communicates with main process via IPC through preload
|
|
94
|
+
|
|
95
|
+
Preload Script (Bridge)
|
|
96
|
+
- Runs before renderer page loads
|
|
97
|
+
- Has access to a limited set of Node.js/Electron APIs
|
|
98
|
+
- Uses contextBridge to expose safe API to renderer
|
|
99
|
+
- The ONLY bridge between renderer and main process
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Main Process Entry Point
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// src/main/index.ts
|
|
106
|
+
import { app, BrowserWindow } from "electron";
|
|
107
|
+
import path from "node:path";
|
|
108
|
+
import { registerIpcHandlers } from "./ipc-handlers";
|
|
109
|
+
import { createMainWindow } from "./windows/main-window";
|
|
110
|
+
import { createAppMenu } from "./menu";
|
|
111
|
+
import { setupTray } from "./tray";
|
|
112
|
+
import { setupAutoUpdater } from "./updater";
|
|
113
|
+
|
|
114
|
+
// Handle single instance lock
|
|
115
|
+
const gotLock = app.requestSingleInstanceLock();
|
|
116
|
+
if (!gotLock) {
|
|
117
|
+
app.quit();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Store reference to prevent garbage collection
|
|
121
|
+
let mainWindow: BrowserWindow | null = null;
|
|
122
|
+
|
|
123
|
+
app.whenReady().then(async () => {
|
|
124
|
+
// Register IPC handlers BEFORE creating windows
|
|
125
|
+
registerIpcHandlers();
|
|
126
|
+
|
|
127
|
+
// Create the main window
|
|
128
|
+
mainWindow = createMainWindow();
|
|
129
|
+
|
|
130
|
+
// Set up application menu
|
|
131
|
+
createAppMenu();
|
|
132
|
+
|
|
133
|
+
// Set up system tray
|
|
134
|
+
setupTray(mainWindow);
|
|
135
|
+
|
|
136
|
+
// Check for updates (production only)
|
|
137
|
+
if (!app.isPackaged) {
|
|
138
|
+
// Skip in development
|
|
139
|
+
} else {
|
|
140
|
+
setupAutoUpdater(mainWindow);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// macOS: re-create window when dock icon clicked
|
|
144
|
+
app.on("activate", () => {
|
|
145
|
+
if (BrowserWindow.getAllWindows().length === 0) {
|
|
146
|
+
mainWindow = createMainWindow();
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Handle second instance (single instance lock)
|
|
152
|
+
app.on("second-instance", (_event, _commandLine, _workingDirectory) => {
|
|
153
|
+
if (mainWindow) {
|
|
154
|
+
if (mainWindow.isMinimized()) mainWindow.restore();
|
|
155
|
+
mainWindow.focus();
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Quit when all windows closed (except macOS)
|
|
160
|
+
app.on("window-all-closed", () => {
|
|
161
|
+
if (process.platform !== "darwin") {
|
|
162
|
+
app.quit();
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Security: prevent new webview/window creation
|
|
167
|
+
app.on("web-contents-created", (_event, contents) => {
|
|
168
|
+
// Prevent navigation to external URLs
|
|
169
|
+
contents.on("will-navigate", (event, url) => {
|
|
170
|
+
const parsedUrl = new URL(url);
|
|
171
|
+
if (parsedUrl.origin !== "http://localhost:5173") {
|
|
172
|
+
event.preventDefault();
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Prevent opening new windows
|
|
177
|
+
contents.setWindowOpenHandler(({ url }) => {
|
|
178
|
+
// Open external links in the default browser
|
|
179
|
+
if (url.startsWith("https://")) {
|
|
180
|
+
import("electron").then(({ shell }) => shell.openExternal(url));
|
|
181
|
+
}
|
|
182
|
+
return { action: "deny" };
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### BrowserWindow Management
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
// src/main/windows/main-window.ts
|
|
191
|
+
import { BrowserWindow, screen } from "electron";
|
|
192
|
+
import path from "node:path";
|
|
193
|
+
import { getWindowState, saveWindowState } from "../window-state";
|
|
194
|
+
|
|
195
|
+
export function createMainWindow(): BrowserWindow {
|
|
196
|
+
// Restore previous window position/size
|
|
197
|
+
const savedState = getWindowState("main", {
|
|
198
|
+
width: 1200,
|
|
199
|
+
height: 800,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const win = new BrowserWindow({
|
|
203
|
+
width: savedState.width,
|
|
204
|
+
height: savedState.height,
|
|
205
|
+
x: savedState.x,
|
|
206
|
+
y: savedState.y,
|
|
207
|
+
minWidth: 800,
|
|
208
|
+
minHeight: 600,
|
|
209
|
+
title: "My Application",
|
|
210
|
+
show: false, // Show after ready-to-show to prevent flash
|
|
211
|
+
backgroundColor: "#1a1a2e",
|
|
212
|
+
webPreferences: {
|
|
213
|
+
preload: path.join(__dirname, "../preload/index.js"),
|
|
214
|
+
// Security defaults (Electron 30+)
|
|
215
|
+
contextIsolation: true, // MUST be true
|
|
216
|
+
nodeIntegration: false, // MUST be false
|
|
217
|
+
sandbox: true, // MUST be true
|
|
218
|
+
webSecurity: true, // MUST be true
|
|
219
|
+
allowRunningInsecureContent: false,
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Show when ready (prevents white flash)
|
|
224
|
+
win.once("ready-to-show", () => {
|
|
225
|
+
win.show();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Save window state on close
|
|
229
|
+
win.on("close", () => {
|
|
230
|
+
saveWindowState("main", win.getBounds());
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Load the app
|
|
234
|
+
if (process.env.NODE_ENV === "development") {
|
|
235
|
+
win.loadURL("http://localhost:5173");
|
|
236
|
+
win.webContents.openDevTools();
|
|
237
|
+
} else {
|
|
238
|
+
win.loadFile(path.join(__dirname, "../renderer/index.html"));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return win;
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Window State Persistence
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
// src/main/window-state.ts
|
|
249
|
+
import { app } from "electron";
|
|
250
|
+
import fs from "node:fs";
|
|
251
|
+
import path from "node:path";
|
|
252
|
+
|
|
253
|
+
interface WindowState {
|
|
254
|
+
x?: number;
|
|
255
|
+
y?: number;
|
|
256
|
+
width: number;
|
|
257
|
+
height: number;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const stateFile = path.join(app.getPath("userData"), "window-state.json");
|
|
261
|
+
|
|
262
|
+
function loadStateFile(): Record<string, WindowState> {
|
|
263
|
+
try {
|
|
264
|
+
const data = fs.readFileSync(stateFile, "utf-8");
|
|
265
|
+
return JSON.parse(data);
|
|
266
|
+
} catch {
|
|
267
|
+
return {};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function getWindowState(
|
|
272
|
+
key: string,
|
|
273
|
+
defaults: WindowState
|
|
274
|
+
): WindowState {
|
|
275
|
+
const states = loadStateFile();
|
|
276
|
+
return states[key] || defaults;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function saveWindowState(
|
|
280
|
+
key: string,
|
|
281
|
+
bounds: Electron.Rectangle
|
|
282
|
+
): void {
|
|
283
|
+
const states = loadStateFile();
|
|
284
|
+
states[key] = {
|
|
285
|
+
x: bounds.x,
|
|
286
|
+
y: bounds.y,
|
|
287
|
+
width: bounds.width,
|
|
288
|
+
height: bounds.height,
|
|
289
|
+
};
|
|
290
|
+
fs.writeFileSync(stateFile, JSON.stringify(states, null, 2));
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Preload Script and contextBridge
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
// src/preload/index.ts
|
|
298
|
+
import { contextBridge, ipcRenderer } from "electron";
|
|
299
|
+
|
|
300
|
+
// Define the API exposed to the renderer
|
|
301
|
+
const electronAPI = {
|
|
302
|
+
// --- File Operations ---
|
|
303
|
+
readFile: (filePath: string): Promise<string> =>
|
|
304
|
+
ipcRenderer.invoke("file:read", filePath),
|
|
305
|
+
|
|
306
|
+
writeFile: (filePath: string, content: string): Promise<void> =>
|
|
307
|
+
ipcRenderer.invoke("file:write", filePath, content),
|
|
308
|
+
|
|
309
|
+
showOpenDialog: (
|
|
310
|
+
options: Electron.OpenDialogOptions
|
|
311
|
+
): Promise<Electron.OpenDialogReturnValue> =>
|
|
312
|
+
ipcRenderer.invoke("dialog:open", options),
|
|
313
|
+
|
|
314
|
+
showSaveDialog: (
|
|
315
|
+
options: Electron.SaveDialogOptions
|
|
316
|
+
): Promise<Electron.SaveDialogReturnValue> =>
|
|
317
|
+
ipcRenderer.invoke("dialog:save", options),
|
|
318
|
+
|
|
319
|
+
// --- App Info ---
|
|
320
|
+
getAppVersion: (): Promise<string> =>
|
|
321
|
+
ipcRenderer.invoke("app:version"),
|
|
322
|
+
|
|
323
|
+
getPlatform: (): NodeJS.Platform =>
|
|
324
|
+
process.platform as NodeJS.Platform,
|
|
325
|
+
|
|
326
|
+
// --- Window Controls ---
|
|
327
|
+
minimizeWindow: (): void =>
|
|
328
|
+
ipcRenderer.send("window:minimize"),
|
|
329
|
+
|
|
330
|
+
maximizeWindow: (): void =>
|
|
331
|
+
ipcRenderer.send("window:maximize"),
|
|
332
|
+
|
|
333
|
+
closeWindow: (): void =>
|
|
334
|
+
ipcRenderer.send("window:close"),
|
|
335
|
+
|
|
336
|
+
// --- Events (main -> renderer) ---
|
|
337
|
+
onUpdateAvailable: (callback: (version: string) => void) => {
|
|
338
|
+
const handler = (_event: Electron.IpcRendererEvent, version: string) =>
|
|
339
|
+
callback(version);
|
|
340
|
+
ipcRenderer.on("update:available", handler);
|
|
341
|
+
// Return cleanup function
|
|
342
|
+
return () => ipcRenderer.removeListener("update:available", handler);
|
|
343
|
+
},
|
|
344
|
+
|
|
345
|
+
onMenuAction: (callback: (action: string) => void) => {
|
|
346
|
+
const handler = (_event: Electron.IpcRendererEvent, action: string) =>
|
|
347
|
+
callback(action);
|
|
348
|
+
ipcRenderer.on("menu:action", handler);
|
|
349
|
+
return () => ipcRenderer.removeListener("menu:action", handler);
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// Expose to renderer via window.electronAPI
|
|
354
|
+
contextBridge.exposeInMainWorld("electronAPI", electronAPI);
|
|
355
|
+
|
|
356
|
+
// Export type for use in renderer
|
|
357
|
+
export type ElectronAPI = typeof electronAPI;
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### IPC Handlers (Main Process)
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
// src/main/ipc-handlers.ts
|
|
364
|
+
import { ipcMain, dialog, app, BrowserWindow } from "electron";
|
|
365
|
+
import fs from "node:fs/promises";
|
|
366
|
+
import path from "node:path";
|
|
367
|
+
|
|
368
|
+
export function registerIpcHandlers(): void {
|
|
369
|
+
// --- File Operations ---
|
|
370
|
+
ipcMain.handle(
|
|
371
|
+
"file:read",
|
|
372
|
+
async (_event, filePath: string): Promise<string> => {
|
|
373
|
+
// Validate path to prevent directory traversal
|
|
374
|
+
const resolved = path.resolve(filePath);
|
|
375
|
+
if (!isAllowedPath(resolved)) {
|
|
376
|
+
throw new Error("Access denied: path outside allowed directories");
|
|
377
|
+
}
|
|
378
|
+
return fs.readFile(resolved, "utf-8");
|
|
379
|
+
}
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
ipcMain.handle(
|
|
383
|
+
"file:write",
|
|
384
|
+
async (_event, filePath: string, content: string): Promise<void> => {
|
|
385
|
+
const resolved = path.resolve(filePath);
|
|
386
|
+
if (!isAllowedPath(resolved)) {
|
|
387
|
+
throw new Error("Access denied: path outside allowed directories");
|
|
388
|
+
}
|
|
389
|
+
await fs.writeFile(resolved, content, "utf-8");
|
|
390
|
+
}
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
// --- Dialogs ---
|
|
394
|
+
ipcMain.handle(
|
|
395
|
+
"dialog:open",
|
|
396
|
+
async (event, options: Electron.OpenDialogOptions) => {
|
|
397
|
+
const win = BrowserWindow.fromWebContents(event.sender);
|
|
398
|
+
if (!win) throw new Error("No window found");
|
|
399
|
+
return dialog.showOpenDialog(win, options);
|
|
400
|
+
}
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
ipcMain.handle(
|
|
404
|
+
"dialog:save",
|
|
405
|
+
async (event, options: Electron.SaveDialogOptions) => {
|
|
406
|
+
const win = BrowserWindow.fromWebContents(event.sender);
|
|
407
|
+
if (!win) throw new Error("No window found");
|
|
408
|
+
return dialog.showSaveDialog(win, options);
|
|
409
|
+
}
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
// --- App Info ---
|
|
413
|
+
ipcMain.handle("app:version", () => app.getVersion());
|
|
414
|
+
|
|
415
|
+
// --- Window Controls ---
|
|
416
|
+
ipcMain.on("window:minimize", (event) => {
|
|
417
|
+
BrowserWindow.fromWebContents(event.sender)?.minimize();
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
ipcMain.on("window:maximize", (event) => {
|
|
421
|
+
const win = BrowserWindow.fromWebContents(event.sender);
|
|
422
|
+
if (win) {
|
|
423
|
+
win.isMaximized() ? win.unmaximize() : win.maximize();
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
ipcMain.on("window:close", (event) => {
|
|
428
|
+
BrowserWindow.fromWebContents(event.sender)?.close();
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Security: validate file paths
|
|
433
|
+
function isAllowedPath(filePath: string): boolean {
|
|
434
|
+
const userDataPath = app.getPath("userData");
|
|
435
|
+
const documentsPath = app.getPath("documents");
|
|
436
|
+
const allowedRoots = [userDataPath, documentsPath];
|
|
437
|
+
|
|
438
|
+
return allowedRoots.some((root) => filePath.startsWith(root));
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### Typed API in Renderer
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
// src/renderer/lib/electron-api.ts
|
|
446
|
+
// Import the type from preload (build-time only, not runtime)
|
|
447
|
+
import type { ElectronAPI } from "../../preload/index";
|
|
448
|
+
|
|
449
|
+
// Augment the Window interface
|
|
450
|
+
declare global {
|
|
451
|
+
interface Window {
|
|
452
|
+
electronAPI: ElectronAPI;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Re-export for convenient access
|
|
457
|
+
export const electronAPI = window.electronAPI;
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
```tsx
|
|
461
|
+
// src/renderer/App.tsx
|
|
462
|
+
import { useState, useEffect } from "react";
|
|
463
|
+
import { electronAPI } from "./lib/electron-api";
|
|
464
|
+
|
|
465
|
+
function App() {
|
|
466
|
+
const [version, setVersion] = useState("");
|
|
467
|
+
const [fileContent, setFileContent] = useState("");
|
|
468
|
+
|
|
469
|
+
useEffect(() => {
|
|
470
|
+
electronAPI.getAppVersion().then(setVersion);
|
|
471
|
+
|
|
472
|
+
// Listen for menu actions
|
|
473
|
+
const cleanup = electronAPI.onMenuAction((action) => {
|
|
474
|
+
switch (action) {
|
|
475
|
+
case "open-file":
|
|
476
|
+
handleOpenFile();
|
|
477
|
+
break;
|
|
478
|
+
case "save-file":
|
|
479
|
+
handleSaveFile();
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
return cleanup;
|
|
485
|
+
}, []);
|
|
486
|
+
|
|
487
|
+
async function handleOpenFile() {
|
|
488
|
+
const result = await electronAPI.showOpenDialog({
|
|
489
|
+
filters: [
|
|
490
|
+
{ name: "Text Files", extensions: ["txt", "md"] },
|
|
491
|
+
{ name: "All Files", extensions: ["*"] },
|
|
492
|
+
],
|
|
493
|
+
properties: ["openFile"],
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
if (!result.canceled && result.filePaths[0]) {
|
|
497
|
+
const content = await electronAPI.readFile(result.filePaths[0]);
|
|
498
|
+
setFileContent(content);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
async function handleSaveFile() {
|
|
503
|
+
const result = await electronAPI.showSaveDialog({
|
|
504
|
+
filters: [{ name: "Text Files", extensions: ["txt"] }],
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
if (!result.canceled && result.filePath) {
|
|
508
|
+
await electronAPI.writeFile(result.filePath, fileContent);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return (
|
|
513
|
+
<div>
|
|
514
|
+
<header>
|
|
515
|
+
<span>My App v{version}</span>
|
|
516
|
+
<div className="window-controls">
|
|
517
|
+
<button onClick={() => electronAPI.minimizeWindow()}>-</button>
|
|
518
|
+
<button onClick={() => electronAPI.maximizeWindow()}>[]</button>
|
|
519
|
+
<button onClick={() => electronAPI.closeWindow()}>X</button>
|
|
520
|
+
</div>
|
|
521
|
+
</header>
|
|
522
|
+
<main>
|
|
523
|
+
<textarea
|
|
524
|
+
value={fileContent}
|
|
525
|
+
onChange={(e) => setFileContent(e.target.value)}
|
|
526
|
+
/>
|
|
527
|
+
</main>
|
|
528
|
+
</div>
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### IPC Patterns: invoke vs send
|
|
534
|
+
|
|
535
|
+
```typescript
|
|
536
|
+
// PATTERN 1: invoke/handle (request-response, returns a promise)
|
|
537
|
+
// Use for: requesting data, performing actions that return results
|
|
538
|
+
|
|
539
|
+
// Main process:
|
|
540
|
+
ipcMain.handle("db:query", async (_event, sql: string) => {
|
|
541
|
+
return database.query(sql);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// Renderer (via preload):
|
|
545
|
+
const result = await electronAPI.queryDatabase("SELECT * FROM users");
|
|
546
|
+
|
|
547
|
+
// PATTERN 2: send/on (fire-and-forget, no response)
|
|
548
|
+
// Use for: notifications, window control, one-way commands
|
|
549
|
+
|
|
550
|
+
// Renderer sends:
|
|
551
|
+
ipcRenderer.send("window:minimize");
|
|
552
|
+
|
|
553
|
+
// Main process listens:
|
|
554
|
+
ipcMain.on("window:minimize", (event) => {
|
|
555
|
+
BrowserWindow.fromWebContents(event.sender)?.minimize();
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
// PATTERN 3: Main-to-renderer (push from main process)
|
|
559
|
+
// Use for: update notifications, menu clicks, system events
|
|
560
|
+
|
|
561
|
+
// Main process pushes:
|
|
562
|
+
mainWindow.webContents.send("update:available", "2.0.0");
|
|
563
|
+
|
|
564
|
+
// Renderer listens (via preload):
|
|
565
|
+
ipcRenderer.on("update:available", (_event, version) => {
|
|
566
|
+
showUpdateBanner(version);
|
|
567
|
+
});
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
### Native Menus
|
|
571
|
+
|
|
572
|
+
```typescript
|
|
573
|
+
// src/main/menu.ts
|
|
574
|
+
import { Menu, app, BrowserWindow, shell } from "electron";
|
|
575
|
+
|
|
576
|
+
export function createAppMenu(): void {
|
|
577
|
+
const isMac = process.platform === "darwin";
|
|
578
|
+
|
|
579
|
+
const template: Electron.MenuItemConstructorOptions[] = [
|
|
580
|
+
// macOS app menu
|
|
581
|
+
...(isMac
|
|
582
|
+
? [
|
|
583
|
+
{
|
|
584
|
+
label: app.name,
|
|
585
|
+
submenu: [
|
|
586
|
+
{ role: "about" as const },
|
|
587
|
+
{ type: "separator" as const },
|
|
588
|
+
{ role: "services" as const },
|
|
589
|
+
{ type: "separator" as const },
|
|
590
|
+
{ role: "hide" as const },
|
|
591
|
+
{ role: "hideOthers" as const },
|
|
592
|
+
{ role: "unhide" as const },
|
|
593
|
+
{ type: "separator" as const },
|
|
594
|
+
{ role: "quit" as const },
|
|
595
|
+
],
|
|
596
|
+
},
|
|
597
|
+
]
|
|
598
|
+
: []),
|
|
599
|
+
|
|
600
|
+
// File menu
|
|
601
|
+
{
|
|
602
|
+
label: "File",
|
|
603
|
+
submenu: [
|
|
604
|
+
{
|
|
605
|
+
label: "Open File",
|
|
606
|
+
accelerator: "CmdOrCtrl+O",
|
|
607
|
+
click: (_item, window) => {
|
|
608
|
+
window?.webContents.send("menu:action", "open-file");
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
label: "Save",
|
|
613
|
+
accelerator: "CmdOrCtrl+S",
|
|
614
|
+
click: (_item, window) => {
|
|
615
|
+
window?.webContents.send("menu:action", "save-file");
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
{ type: "separator" },
|
|
619
|
+
isMac ? { role: "close" } : { role: "quit" },
|
|
620
|
+
],
|
|
621
|
+
},
|
|
622
|
+
|
|
623
|
+
// Edit menu
|
|
624
|
+
{
|
|
625
|
+
label: "Edit",
|
|
626
|
+
submenu: [
|
|
627
|
+
{ role: "undo" },
|
|
628
|
+
{ role: "redo" },
|
|
629
|
+
{ type: "separator" },
|
|
630
|
+
{ role: "cut" },
|
|
631
|
+
{ role: "copy" },
|
|
632
|
+
{ role: "paste" },
|
|
633
|
+
{ role: "selectAll" },
|
|
634
|
+
],
|
|
635
|
+
},
|
|
636
|
+
|
|
637
|
+
// View menu
|
|
638
|
+
{
|
|
639
|
+
label: "View",
|
|
640
|
+
submenu: [
|
|
641
|
+
{ role: "reload" },
|
|
642
|
+
{ role: "forceReload" },
|
|
643
|
+
{ role: "toggleDevTools" },
|
|
644
|
+
{ type: "separator" },
|
|
645
|
+
{ role: "resetZoom" },
|
|
646
|
+
{ role: "zoomIn" },
|
|
647
|
+
{ role: "zoomOut" },
|
|
648
|
+
{ type: "separator" },
|
|
649
|
+
{ role: "togglefullscreen" },
|
|
650
|
+
],
|
|
651
|
+
},
|
|
652
|
+
|
|
653
|
+
// Help menu
|
|
654
|
+
{
|
|
655
|
+
label: "Help",
|
|
656
|
+
submenu: [
|
|
657
|
+
{
|
|
658
|
+
label: "Documentation",
|
|
659
|
+
click: () => {
|
|
660
|
+
shell.openExternal("https://docs.myapp.com");
|
|
661
|
+
},
|
|
662
|
+
},
|
|
663
|
+
],
|
|
664
|
+
},
|
|
665
|
+
];
|
|
666
|
+
|
|
667
|
+
const menu = Menu.buildFromTemplate(template);
|
|
668
|
+
Menu.setApplicationMenu(menu);
|
|
669
|
+
}
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
### System Tray
|
|
673
|
+
|
|
674
|
+
```typescript
|
|
675
|
+
// src/main/tray.ts
|
|
676
|
+
import { Tray, Menu, nativeImage, BrowserWindow, app } from "electron";
|
|
677
|
+
import path from "node:path";
|
|
678
|
+
|
|
679
|
+
let tray: Tray | null = null;
|
|
680
|
+
|
|
681
|
+
export function setupTray(mainWindow: BrowserWindow): void {
|
|
682
|
+
const iconPath = path.join(__dirname, "../../resources/tray-icon.png");
|
|
683
|
+
const icon = nativeImage.createFromPath(iconPath).resize({ width: 16, height: 16 });
|
|
684
|
+
|
|
685
|
+
tray = new Tray(icon);
|
|
686
|
+
|
|
687
|
+
const contextMenu = Menu.buildFromTemplate([
|
|
688
|
+
{
|
|
689
|
+
label: "Show App",
|
|
690
|
+
click: () => {
|
|
691
|
+
mainWindow.show();
|
|
692
|
+
mainWindow.focus();
|
|
693
|
+
},
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
label: "Minimize to Tray",
|
|
697
|
+
click: () => {
|
|
698
|
+
mainWindow.hide();
|
|
699
|
+
},
|
|
700
|
+
},
|
|
701
|
+
{ type: "separator" },
|
|
702
|
+
{
|
|
703
|
+
label: "Quit",
|
|
704
|
+
click: () => {
|
|
705
|
+
app.quit();
|
|
706
|
+
},
|
|
707
|
+
},
|
|
708
|
+
]);
|
|
709
|
+
|
|
710
|
+
tray.setToolTip("My Application");
|
|
711
|
+
tray.setContextMenu(contextMenu);
|
|
712
|
+
|
|
713
|
+
// Click tray icon to show window
|
|
714
|
+
tray.on("click", () => {
|
|
715
|
+
if (mainWindow.isVisible()) {
|
|
716
|
+
mainWindow.hide();
|
|
717
|
+
} else {
|
|
718
|
+
mainWindow.show();
|
|
719
|
+
mainWindow.focus();
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
// Minimize to tray instead of closing
|
|
724
|
+
mainWindow.on("close", (event) => {
|
|
725
|
+
if (!app.isQuitting) {
|
|
726
|
+
event.preventDefault();
|
|
727
|
+
mainWindow.hide();
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
### Auto-Updater
|
|
734
|
+
|
|
735
|
+
```typescript
|
|
736
|
+
// src/main/updater.ts
|
|
737
|
+
import { autoUpdater } from "electron-updater";
|
|
738
|
+
import { BrowserWindow } from "electron";
|
|
739
|
+
import log from "electron-log";
|
|
740
|
+
|
|
741
|
+
export function setupAutoUpdater(mainWindow: BrowserWindow): void {
|
|
742
|
+
// Configure logging
|
|
743
|
+
autoUpdater.logger = log;
|
|
744
|
+
|
|
745
|
+
// Disable auto-download (let user decide)
|
|
746
|
+
autoUpdater.autoDownload = false;
|
|
747
|
+
autoUpdater.autoInstallOnAppQuit = true;
|
|
748
|
+
|
|
749
|
+
autoUpdater.on("checking-for-update", () => {
|
|
750
|
+
log.info("Checking for updates...");
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
autoUpdater.on("update-available", (info) => {
|
|
754
|
+
log.info("Update available:", info.version);
|
|
755
|
+
mainWindow.webContents.send("update:available", info.version);
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
autoUpdater.on("update-not-available", () => {
|
|
759
|
+
log.info("No updates available");
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
autoUpdater.on("download-progress", (progress) => {
|
|
763
|
+
mainWindow.webContents.send("update:progress", {
|
|
764
|
+
percent: progress.percent,
|
|
765
|
+
bytesPerSecond: progress.bytesPerSecond,
|
|
766
|
+
transferred: progress.transferred,
|
|
767
|
+
total: progress.total,
|
|
768
|
+
});
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
autoUpdater.on("update-downloaded", () => {
|
|
772
|
+
mainWindow.webContents.send("update:downloaded");
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
autoUpdater.on("error", (err) => {
|
|
776
|
+
log.error("Update error:", err);
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
// Check for updates every 4 hours
|
|
780
|
+
autoUpdater.checkForUpdates();
|
|
781
|
+
setInterval(
|
|
782
|
+
() => autoUpdater.checkForUpdates(),
|
|
783
|
+
4 * 60 * 60 * 1000
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Called from IPC when user clicks "Install Update"
|
|
788
|
+
export function installUpdate(): void {
|
|
789
|
+
autoUpdater.quitAndInstall();
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Called from IPC when user clicks "Download Update"
|
|
793
|
+
export function downloadUpdate(): void {
|
|
794
|
+
autoUpdater.downloadUpdate();
|
|
795
|
+
}
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
### Protocol Handlers
|
|
799
|
+
|
|
800
|
+
```typescript
|
|
801
|
+
// src/main/index.ts
|
|
802
|
+
import { app, protocol, net } from "electron";
|
|
803
|
+
import path from "node:path";
|
|
804
|
+
import fs from "node:fs";
|
|
805
|
+
|
|
806
|
+
// Register a custom protocol for loading app assets
|
|
807
|
+
app.whenReady().then(() => {
|
|
808
|
+
// Handle app:// protocol for loading local files securely
|
|
809
|
+
protocol.handle("app", (request) => {
|
|
810
|
+
const url = new URL(request.url);
|
|
811
|
+
const filePath = path.join(
|
|
812
|
+
__dirname,
|
|
813
|
+
"../renderer",
|
|
814
|
+
url.pathname
|
|
815
|
+
);
|
|
816
|
+
|
|
817
|
+
// Security: prevent path traversal
|
|
818
|
+
const resolvedPath = path.resolve(filePath);
|
|
819
|
+
const rendererDir = path.resolve(__dirname, "../renderer");
|
|
820
|
+
|
|
821
|
+
if (!resolvedPath.startsWith(rendererDir)) {
|
|
822
|
+
return new Response("Forbidden", { status: 403 });
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
return net.fetch(`file://${resolvedPath}`);
|
|
826
|
+
});
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
// Register as default protocol handler (deep linking)
|
|
830
|
+
if (process.defaultApp) {
|
|
831
|
+
if (process.argv.length >= 2) {
|
|
832
|
+
app.setAsDefaultProtocolClient("myapp", process.execPath, [
|
|
833
|
+
path.resolve(process.argv[1]),
|
|
834
|
+
]);
|
|
835
|
+
}
|
|
836
|
+
} else {
|
|
837
|
+
app.setAsDefaultProtocolClient("myapp");
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// Handle protocol URLs on macOS
|
|
841
|
+
app.on("open-url", (_event, url) => {
|
|
842
|
+
handleDeepLink(url);
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
function handleDeepLink(url: string): void {
|
|
846
|
+
const parsed = new URL(url);
|
|
847
|
+
// myapp://open?file=/path/to/file
|
|
848
|
+
if (parsed.hostname === "open") {
|
|
849
|
+
const filePath = parsed.searchParams.get("file");
|
|
850
|
+
if (filePath) {
|
|
851
|
+
// Send to renderer
|
|
852
|
+
BrowserWindow.getAllWindows()[0]?.webContents.send(
|
|
853
|
+
"deep-link:open-file",
|
|
854
|
+
filePath
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
## Security Best Practices
|
|
862
|
+
|
|
863
|
+
### Security Checklist
|
|
864
|
+
|
|
865
|
+
```typescript
|
|
866
|
+
// MANDATORY security settings for every BrowserWindow
|
|
867
|
+
const win = new BrowserWindow({
|
|
868
|
+
webPreferences: {
|
|
869
|
+
// These MUST be set correctly:
|
|
870
|
+
contextIsolation: true, // Isolate preload from renderer
|
|
871
|
+
nodeIntegration: false, // No Node.js in renderer
|
|
872
|
+
sandbox: true, // OS-level sandboxing
|
|
873
|
+
webSecurity: true, // Same-origin policy
|
|
874
|
+
allowRunningInsecureContent: false,
|
|
875
|
+
|
|
876
|
+
// These should be disabled unless specifically needed:
|
|
877
|
+
webviewTag: false, // No <webview> tags
|
|
878
|
+
navigateOnDragDrop: false, // No drag-and-drop navigation
|
|
879
|
+
|
|
880
|
+
// Preload script is the ONLY bridge
|
|
881
|
+
preload: path.join(__dirname, "preload.js"),
|
|
882
|
+
},
|
|
883
|
+
});
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
### Content Security Policy
|
|
887
|
+
|
|
888
|
+
```html
|
|
889
|
+
<!-- src/renderer/index.html -->
|
|
890
|
+
<meta
|
|
891
|
+
http-equiv="Content-Security-Policy"
|
|
892
|
+
content="
|
|
893
|
+
default-src 'self';
|
|
894
|
+
script-src 'self';
|
|
895
|
+
style-src 'self' 'unsafe-inline';
|
|
896
|
+
img-src 'self' data: https:;
|
|
897
|
+
font-src 'self';
|
|
898
|
+
connect-src 'self' https://api.myapp.com;
|
|
899
|
+
"
|
|
900
|
+
/>
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
### IPC Validation
|
|
904
|
+
|
|
905
|
+
```typescript
|
|
906
|
+
// src/main/ipc-handlers.ts
|
|
907
|
+
import { z } from "zod";
|
|
908
|
+
|
|
909
|
+
// Define schemas for IPC messages
|
|
910
|
+
const FileReadSchema = z.object({
|
|
911
|
+
path: z.string().min(1).max(1024),
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
const FileWriteSchema = z.object({
|
|
915
|
+
path: z.string().min(1).max(1024),
|
|
916
|
+
content: z.string().max(10 * 1024 * 1024), // 10MB max
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
ipcMain.handle("file:read", async (_event, rawPath: unknown) => {
|
|
920
|
+
// Validate input
|
|
921
|
+
const parsed = FileReadSchema.safeParse({ path: rawPath });
|
|
922
|
+
if (!parsed.success) {
|
|
923
|
+
throw new Error(`Invalid input: ${parsed.error.message}`);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
const filePath = path.resolve(parsed.data.path);
|
|
927
|
+
if (!isAllowedPath(filePath)) {
|
|
928
|
+
throw new Error("Access denied");
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
return fs.readFile(filePath, "utf-8");
|
|
932
|
+
});
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
## Performance Optimization
|
|
936
|
+
|
|
937
|
+
### Startup Time
|
|
938
|
+
|
|
939
|
+
```typescript
|
|
940
|
+
// Lazy-load heavy modules
|
|
941
|
+
app.whenReady().then(async () => {
|
|
942
|
+
// Show window immediately with skeleton
|
|
943
|
+
const win = createMainWindow();
|
|
944
|
+
|
|
945
|
+
// Load heavy modules after window is visible
|
|
946
|
+
const { initializeDatabase } = await import("./database");
|
|
947
|
+
const { syncData } = await import("./sync");
|
|
948
|
+
|
|
949
|
+
await initializeDatabase();
|
|
950
|
+
await syncData();
|
|
951
|
+
|
|
952
|
+
// Tell renderer initialization is complete
|
|
953
|
+
win.webContents.send("app:ready");
|
|
954
|
+
});
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
### Memory Management
|
|
958
|
+
|
|
959
|
+
```typescript
|
|
960
|
+
// Destroy windows properly to free memory
|
|
961
|
+
function closeSettingsWindow(): void {
|
|
962
|
+
if (settingsWindow) {
|
|
963
|
+
settingsWindow.destroy(); // Not just close()
|
|
964
|
+
settingsWindow = null; // Allow garbage collection
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// Monitor memory usage
|
|
969
|
+
setInterval(() => {
|
|
970
|
+
const usage = process.memoryUsage();
|
|
971
|
+
if (usage.heapUsed > 500 * 1024 * 1024) { // 500MB threshold
|
|
972
|
+
console.warn("High memory usage:", {
|
|
973
|
+
heapUsed: `${Math.round(usage.heapUsed / 1024 / 1024)}MB`,
|
|
974
|
+
rss: `${Math.round(usage.rss / 1024 / 1024)}MB`,
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
}, 30_000);
|
|
978
|
+
|
|
979
|
+
// Renderer: clean up event listeners
|
|
980
|
+
useEffect(() => {
|
|
981
|
+
const cleanup = electronAPI.onMenuAction(handleAction);
|
|
982
|
+
return () => cleanup(); // Always clean up
|
|
983
|
+
}, []);
|
|
984
|
+
```
|
|
985
|
+
|
|
986
|
+
### Renderer Performance
|
|
987
|
+
|
|
988
|
+
```typescript
|
|
989
|
+
// Use web workers for heavy computation
|
|
990
|
+
const worker = new Worker(new URL("./worker.ts", import.meta.url));
|
|
991
|
+
|
|
992
|
+
worker.postMessage({ type: "parse", data: largeFileContent });
|
|
993
|
+
worker.onmessage = (event) => {
|
|
994
|
+
setResults(event.data);
|
|
995
|
+
};
|
|
996
|
+
|
|
997
|
+
// Use virtual scrolling for large lists
|
|
998
|
+
// (Use a library like react-window or tanstack-virtual)
|
|
999
|
+
|
|
1000
|
+
// Debounce IPC calls
|
|
1001
|
+
import { debounce } from "./utils";
|
|
1002
|
+
|
|
1003
|
+
const debouncedSave = debounce(async (content: string) => {
|
|
1004
|
+
await electronAPI.writeFile(currentFilePath, content);
|
|
1005
|
+
}, 1000);
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
## Build and Distribution
|
|
1009
|
+
|
|
1010
|
+
### Electron Forge Configuration
|
|
1011
|
+
|
|
1012
|
+
```typescript
|
|
1013
|
+
// forge.config.ts
|
|
1014
|
+
import type { ForgeConfig } from "@electron-forge/shared-types";
|
|
1015
|
+
import { MakerSquirrel } from "@electron-forge/maker-squirrel";
|
|
1016
|
+
import { MakerDMG } from "@electron-forge/maker-dmg";
|
|
1017
|
+
import { MakerDeb } from "@electron-forge/maker-deb";
|
|
1018
|
+
import { MakerRpm } from "@electron-forge/maker-rpm";
|
|
1019
|
+
import { VitePlugin } from "@electron-forge/plugin-vite";
|
|
1020
|
+
|
|
1021
|
+
const config: ForgeConfig = {
|
|
1022
|
+
packagerConfig: {
|
|
1023
|
+
name: "My App",
|
|
1024
|
+
executableName: "my-app",
|
|
1025
|
+
icon: "./resources/icon",
|
|
1026
|
+
asar: true,
|
|
1027
|
+
appBundleId: "com.mycompany.myapp",
|
|
1028
|
+
// macOS signing
|
|
1029
|
+
osxSign: {},
|
|
1030
|
+
osxNotarize: {
|
|
1031
|
+
appleId: process.env.APPLE_ID || "",
|
|
1032
|
+
appleIdPassword: process.env.APPLE_PASSWORD || "",
|
|
1033
|
+
teamId: process.env.APPLE_TEAM_ID || "",
|
|
1034
|
+
},
|
|
1035
|
+
},
|
|
1036
|
+
makers: [
|
|
1037
|
+
new MakerSquirrel({
|
|
1038
|
+
name: "my-app",
|
|
1039
|
+
setupIcon: "./resources/icon.ico",
|
|
1040
|
+
}),
|
|
1041
|
+
new MakerDMG({
|
|
1042
|
+
icon: "./resources/icon.icns",
|
|
1043
|
+
}),
|
|
1044
|
+
new MakerDeb({
|
|
1045
|
+
options: {
|
|
1046
|
+
icon: "./resources/icon.png",
|
|
1047
|
+
categories: ["Utility"],
|
|
1048
|
+
},
|
|
1049
|
+
}),
|
|
1050
|
+
new MakerRpm({}),
|
|
1051
|
+
],
|
|
1052
|
+
plugins: [
|
|
1053
|
+
new VitePlugin({
|
|
1054
|
+
build: [
|
|
1055
|
+
{ entry: "src/main/index.ts", config: "vite.main.config.ts" },
|
|
1056
|
+
{ entry: "src/preload/index.ts", config: "vite.preload.config.ts" },
|
|
1057
|
+
],
|
|
1058
|
+
renderer: [
|
|
1059
|
+
{
|
|
1060
|
+
name: "main_window",
|
|
1061
|
+
config: "vite.renderer.config.ts",
|
|
1062
|
+
},
|
|
1063
|
+
],
|
|
1064
|
+
}),
|
|
1065
|
+
],
|
|
1066
|
+
};
|
|
1067
|
+
|
|
1068
|
+
export default config;
|
|
1069
|
+
```
|
|
1070
|
+
|
|
1071
|
+
### electron-builder Configuration
|
|
1072
|
+
|
|
1073
|
+
```yaml
|
|
1074
|
+
# electron-builder.yml
|
|
1075
|
+
appId: com.mycompany.myapp
|
|
1076
|
+
productName: My App
|
|
1077
|
+
directories:
|
|
1078
|
+
output: dist
|
|
1079
|
+
buildResources: resources
|
|
1080
|
+
|
|
1081
|
+
files:
|
|
1082
|
+
- "out/**/*"
|
|
1083
|
+
- "package.json"
|
|
1084
|
+
|
|
1085
|
+
asar: true
|
|
1086
|
+
|
|
1087
|
+
win:
|
|
1088
|
+
target:
|
|
1089
|
+
- nsis
|
|
1090
|
+
- portable
|
|
1091
|
+
icon: resources/icon.ico
|
|
1092
|
+
|
|
1093
|
+
mac:
|
|
1094
|
+
target:
|
|
1095
|
+
- dmg
|
|
1096
|
+
- zip
|
|
1097
|
+
icon: resources/icon.icns
|
|
1098
|
+
category: public.app-category.developer-tools
|
|
1099
|
+
hardenedRuntime: true
|
|
1100
|
+
gatekeeperAssess: false
|
|
1101
|
+
entitlements: build/entitlements.mac.plist
|
|
1102
|
+
|
|
1103
|
+
linux:
|
|
1104
|
+
target:
|
|
1105
|
+
- AppImage
|
|
1106
|
+
- deb
|
|
1107
|
+
- rpm
|
|
1108
|
+
icon: resources/icons
|
|
1109
|
+
category: Utility
|
|
1110
|
+
|
|
1111
|
+
publish:
|
|
1112
|
+
provider: github
|
|
1113
|
+
owner: mycompany
|
|
1114
|
+
repo: my-app
|
|
1115
|
+
```
|
|
1116
|
+
|
|
1117
|
+
### Build Commands
|
|
1118
|
+
|
|
1119
|
+
```bash
|
|
1120
|
+
# Electron Forge
|
|
1121
|
+
npx electron-forge start # Development
|
|
1122
|
+
npx electron-forge package # Package (no installer)
|
|
1123
|
+
npx electron-forge make # Create distributable
|
|
1124
|
+
|
|
1125
|
+
# electron-builder
|
|
1126
|
+
npx electron-builder --mac
|
|
1127
|
+
npx electron-builder --win
|
|
1128
|
+
npx electron-builder --linux
|
|
1129
|
+
npx electron-builder --mac --win --linux # All platforms
|
|
1130
|
+
```
|
|
1131
|
+
|
|
1132
|
+
## Crash Reporting
|
|
1133
|
+
|
|
1134
|
+
```typescript
|
|
1135
|
+
// src/main/index.ts
|
|
1136
|
+
import { crashReporter } from "electron";
|
|
1137
|
+
|
|
1138
|
+
crashReporter.start({
|
|
1139
|
+
productName: "My App",
|
|
1140
|
+
submitURL: "https://crashes.myapp.com/submit",
|
|
1141
|
+
uploadToServer: true,
|
|
1142
|
+
compress: true,
|
|
1143
|
+
extra: {
|
|
1144
|
+
appVersion: app.getVersion(),
|
|
1145
|
+
platform: process.platform,
|
|
1146
|
+
},
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
// Handle uncaught exceptions
|
|
1150
|
+
process.on("uncaughtException", (error) => {
|
|
1151
|
+
log.error("Uncaught exception:", error);
|
|
1152
|
+
// Show error dialog to user
|
|
1153
|
+
dialog.showErrorBox(
|
|
1154
|
+
"Application Error",
|
|
1155
|
+
"An unexpected error occurred. The app will now restart."
|
|
1156
|
+
);
|
|
1157
|
+
app.relaunch();
|
|
1158
|
+
app.exit(1);
|
|
1159
|
+
});
|
|
1160
|
+
```
|
|
1161
|
+
|
|
1162
|
+
## Testing
|
|
1163
|
+
|
|
1164
|
+
### Main Process Tests
|
|
1165
|
+
|
|
1166
|
+
```typescript
|
|
1167
|
+
// tests/main/ipc-handlers.test.ts
|
|
1168
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
1169
|
+
|
|
1170
|
+
// Mock Electron modules
|
|
1171
|
+
vi.mock("electron", () => ({
|
|
1172
|
+
ipcMain: {
|
|
1173
|
+
handle: vi.fn(),
|
|
1174
|
+
on: vi.fn(),
|
|
1175
|
+
},
|
|
1176
|
+
app: {
|
|
1177
|
+
getPath: vi.fn(() => "/mock/user/data"),
|
|
1178
|
+
getVersion: vi.fn(() => "1.0.0"),
|
|
1179
|
+
},
|
|
1180
|
+
dialog: {
|
|
1181
|
+
showOpenDialog: vi.fn(),
|
|
1182
|
+
},
|
|
1183
|
+
BrowserWindow: {
|
|
1184
|
+
fromWebContents: vi.fn(),
|
|
1185
|
+
},
|
|
1186
|
+
}));
|
|
1187
|
+
|
|
1188
|
+
describe("IPC Handlers", () => {
|
|
1189
|
+
it("should validate file paths", () => {
|
|
1190
|
+
const isAllowed = isAllowedPath("/mock/user/data/file.txt");
|
|
1191
|
+
expect(isAllowed).toBe(true);
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
it("should reject paths outside allowed directories", () => {
|
|
1195
|
+
const isAllowed = isAllowedPath("/etc/passwd");
|
|
1196
|
+
expect(isAllowed).toBe(false);
|
|
1197
|
+
});
|
|
1198
|
+
});
|
|
1199
|
+
```
|
|
1200
|
+
|
|
1201
|
+
### E2E Tests with Playwright
|
|
1202
|
+
|
|
1203
|
+
```typescript
|
|
1204
|
+
// tests/e2e/app.spec.ts
|
|
1205
|
+
import { test, expect, _electron as electron } from "@playwright/test";
|
|
1206
|
+
|
|
1207
|
+
let electronApp: Awaited<ReturnType<typeof electron.launch>>;
|
|
1208
|
+
let page: Awaited<ReturnType<typeof electronApp.firstWindow>>;
|
|
1209
|
+
|
|
1210
|
+
test.beforeAll(async () => {
|
|
1211
|
+
electronApp = await electron.launch({
|
|
1212
|
+
args: ["."],
|
|
1213
|
+
env: { NODE_ENV: "test" },
|
|
1214
|
+
});
|
|
1215
|
+
page = await electronApp.firstWindow();
|
|
1216
|
+
});
|
|
1217
|
+
|
|
1218
|
+
test.afterAll(async () => {
|
|
1219
|
+
await electronApp.close();
|
|
1220
|
+
});
|
|
1221
|
+
|
|
1222
|
+
test("should show the main window", async () => {
|
|
1223
|
+
const title = await page.title();
|
|
1224
|
+
expect(title).toBe("My Application");
|
|
1225
|
+
});
|
|
1226
|
+
|
|
1227
|
+
test("should display app version", async () => {
|
|
1228
|
+
const version = await page.locator("[data-testid='app-version']").textContent();
|
|
1229
|
+
expect(version).toMatch(/v\d+\.\d+\.\d+/);
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
test("should open file dialog", async () => {
|
|
1233
|
+
// Mock the dialog at the Electron level
|
|
1234
|
+
await electronApp.evaluate(async ({ dialog }) => {
|
|
1235
|
+
dialog.showOpenDialog = async () => ({
|
|
1236
|
+
canceled: false,
|
|
1237
|
+
filePaths: ["/tmp/test.txt"],
|
|
1238
|
+
});
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
await page.click("button:has-text('Open File')");
|
|
1242
|
+
// Verify file content is displayed
|
|
1243
|
+
await expect(page.locator("textarea")).not.toBeEmpty();
|
|
1244
|
+
});
|
|
1245
|
+
```
|
|
1246
|
+
|
|
1247
|
+
## Anti-Patterns
|
|
1248
|
+
|
|
1249
|
+
| Anti-Pattern | Why It Is Bad | Correct Approach |
|
|
1250
|
+
|--------------|---------------|------------------|
|
|
1251
|
+
| `nodeIntegration: true` | Full Node.js in renderer, massive attack surface | `nodeIntegration: false` + preload |
|
|
1252
|
+
| `contextIsolation: false` | Preload shares context with renderer | `contextIsolation: true` + contextBridge |
|
|
1253
|
+
| `webSecurity: false` | Disables same-origin policy | Keep `webSecurity: true` always |
|
|
1254
|
+
| Direct `require()` in renderer | Security hole, bypasses sandbox | Use contextBridge exposed API |
|
|
1255
|
+
| `remote` module | Deprecated, synchronous IPC, security risk | Use `invoke`/`handle` pattern |
|
|
1256
|
+
| Exposing entire `ipcRenderer` | Renderer can call any IPC channel | Expose specific, named functions only |
|
|
1257
|
+
| Storing secrets in renderer | Webview is inspectable via DevTools | Store in main process, use env vars |
|
|
1258
|
+
| Not destroying closed windows | Memory leak, window references persist | `window.destroy()` + null reference |
|
|
1259
|
+
| Synchronous IPC (`sendSync`) | Blocks renderer thread | Use `invoke` (async) |
|
|
1260
|
+
| Loading remote URLs without CSP | XSS and code injection risk | Strict CSP for all content |
|
|
1261
|
+
| Global `require` in preload | Exposes Node.js modules to renderer | Only expose needed functions via contextBridge |
|
|
1262
|
+
| `shell.openExternal(userInput)` | Arbitrary command execution | Validate URLs against allowlist |
|
|
1263
|
+
|
|
1264
|
+
## Best Practices
|
|
1265
|
+
|
|
1266
|
+
1. **Preload is the only bridge**: Never expose Node.js APIs directly to the renderer
|
|
1267
|
+
2. **Validate all IPC input**: Use Zod schemas for IPC message validation
|
|
1268
|
+
3. **Minimal API surface**: Only expose exactly what the renderer needs
|
|
1269
|
+
4. **Type-safe IPC**: Define TypeScript types for all IPC channels
|
|
1270
|
+
5. **Window state persistence**: Save and restore window position/size
|
|
1271
|
+
6. **Graceful degradation**: Handle cases where IPC fails or times out
|
|
1272
|
+
7. **Single instance lock**: Prevent multiple app instances from running
|
|
1273
|
+
8. **Code sign everything**: Required for macOS notarization and Windows SmartScreen
|
|
1274
|
+
9. **Lazy load heavy modules**: Import expensive modules after window is visible
|
|
1275
|
+
10. **Monitor memory**: Set up alerts for excessive memory usage
|
|
1276
|
+
11. **Test IPC handlers**: Unit test main process handlers without Electron
|
|
1277
|
+
12. **Use Playwright for E2E**: Official Electron testing support
|
|
1278
|
+
|
|
1279
|
+
## References
|
|
1280
|
+
|
|
1281
|
+
- Electron Documentation: https://www.electronjs.org/docs
|
|
1282
|
+
- Electron Security: https://www.electronjs.org/docs/latest/tutorial/security
|
|
1283
|
+
- Electron Forge: https://www.electronforge.io
|
|
1284
|
+
- electron-builder: https://www.electron.build
|
|
1285
|
+
- Electron Fiddle: https://www.electronjs.org/fiddle
|
|
1286
|
+
- Playwright Electron: https://playwright.dev/docs/api/class-electron
|