enya-agent 0.1.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/.env.example +20 -0
- package/.github/workflows/ci.yml +70 -0
- package/.github/workflows/publish.yml +250 -0
- package/.gitmodules +3 -0
- package/Cargo.lock +3584 -0
- package/Cargo.toml +97 -0
- package/crates/enact/Cargo.toml +27 -0
- package/crates/enact/src/lib.rs +60 -0
- package/crates/enact-a2a/Cargo.toml +25 -0
- package/crates/enact-a2a/src/lib.rs +411 -0
- package/crates/enact-channels/Cargo.toml +64 -0
- package/crates/enact-channels/examples/README.md +80 -0
- package/crates/enact-channels/examples/channel_bot.rs +169 -0
- package/crates/enact-channels/examples/telegram-echo.rs +34 -0
- package/crates/enact-channels/examples/whatsapp-echo.rs +142 -0
- package/crates/enact-channels/src/config.rs +213 -0
- package/crates/enact-channels/src/lib.rs +25 -0
- package/crates/enact-channels/src/runtime.rs +237 -0
- package/crates/enact-channels/src/security/mod.rs +5 -0
- package/crates/enact-channels/src/security/pairing.rs +205 -0
- package/crates/enact-channels/src/teams.rs +601 -0
- package/crates/enact-channels/src/telegram.rs +2833 -0
- package/crates/enact-channels/src/traits.rs +200 -0
- package/crates/enact-channels/src/webhook.rs +262 -0
- package/crates/enact-channels/src/whatsapp.rs +310 -0
- package/crates/enact-cli/Cargo.toml +40 -0
- package/crates/enact-cli/src/commands/doctor.rs +62 -0
- package/crates/enact-cli/src/commands/mod.rs +3 -0
- package/crates/enact-cli/src/commands/run.rs +69 -0
- package/crates/enact-cli/src/commands/serve.rs +81 -0
- package/crates/enact-cli/src/config.rs +2 -0
- package/crates/enact-cli/src/main.rs +79 -0
- package/crates/enact-config/Cargo.toml +36 -0
- package/crates/enact-config/ENV_VAR_MAPPING.md +135 -0
- package/crates/enact-config/QUICK_REFERENCE.md +92 -0
- package/crates/enact-config/README.md +107 -0
- package/crates/enact-config/TESTING.md +161 -0
- package/crates/enact-config/examples/test-env-vars.rs +100 -0
- package/crates/enact-config/src/config.rs +399 -0
- package/crates/enact-config/src/encrypted_store.rs +211 -0
- package/crates/enact-config/src/lib.rs +298 -0
- package/crates/enact-config/src/secrets.rs +149 -0
- package/crates/enact-config/src/sync.rs +260 -0
- package/crates/enact-config/test-env-vars.sh +34 -0
- package/crates/enact-config/tests/README.md +99 -0
- package/crates/enact-config/tests/config_integration_test.rs +202 -0
- package/crates/enact-config/tests/security_test.rs +140 -0
- package/crates/enact-context/Cargo.toml +41 -0
- package/crates/enact-context/src/budget.rs +314 -0
- package/crates/enact-context/src/calibrator.rs +535 -0
- package/crates/enact-context/src/compactor.rs +392 -0
- package/crates/enact-context/src/condenser.rs +826 -0
- package/crates/enact-context/src/lib.rs +94 -0
- package/crates/enact-context/src/segment.rs +238 -0
- package/crates/enact-context/src/step_context.rs +645 -0
- package/crates/enact-context/src/token_counter.rs +148 -0
- package/crates/enact-context/src/window.rs +372 -0
- package/crates/enact-core/Cargo.toml +42 -0
- package/crates/enact-core/README.md +98 -0
- package/crates/enact-core/src/background/executor.rs +524 -0
- package/crates/enact-core/src/background/mod.rs +48 -0
- package/crates/enact-core/src/background/target_binding.rs +390 -0
- package/crates/enact-core/src/background/trigger.rs +511 -0
- package/crates/enact-core/src/callable/callable.rs +152 -0
- package/crates/enact-core/src/callable/composite.rs +817 -0
- package/crates/enact-core/src/callable/graph.rs +104 -0
- package/crates/enact-core/src/callable/llm.rs +211 -0
- package/crates/enact-core/src/callable/mod.rs +64 -0
- package/crates/enact-core/src/callable/registry.rs +206 -0
- package/crates/enact-core/src/context/execution_context.rs +757 -0
- package/crates/enact-core/src/context/invocation.rs +99 -0
- package/crates/enact-core/src/context/mod.rs +50 -0
- package/crates/enact-core/src/context/tenant.rs +175 -0
- package/crates/enact-core/src/context/trace.rs +127 -0
- package/crates/enact-core/src/flow/conditional.rs +293 -0
- package/crates/enact-core/src/flow/mod.rs +43 -0
- package/crates/enact-core/src/flow/parallel.rs +437 -0
- package/crates/enact-core/src/flow/repeat.rs +534 -0
- package/crates/enact-core/src/flow/sequential.rs +248 -0
- package/crates/enact-core/src/graph/checkpoint.rs +79 -0
- package/crates/enact-core/src/graph/checkpoint_store.rs +76 -0
- package/crates/enact-core/src/graph/compiled.rs +189 -0
- package/crates/enact-core/src/graph/edge.rs +59 -0
- package/crates/enact-core/src/graph/graph_schema.rs +218 -0
- package/crates/enact-core/src/graph/loader.rs +155 -0
- package/crates/enact-core/src/graph/mod.rs +18 -0
- package/crates/enact-core/src/graph/node/function.rs +49 -0
- package/crates/enact-core/src/graph/node/mod.rs +48 -0
- package/crates/enact-core/src/graph/schema.rs +62 -0
- package/crates/enact-core/src/inbox/message.rs +405 -0
- package/crates/enact-core/src/inbox/mod.rs +31 -0
- package/crates/enact-core/src/inbox/store.rs +355 -0
- package/crates/enact-core/src/kernel/artifact/filesystem.rs +546 -0
- package/crates/enact-core/src/kernel/artifact/metadata.rs +283 -0
- package/crates/enact-core/src/kernel/artifact/mod.rs +27 -0
- package/crates/enact-core/src/kernel/artifact/store.rs +427 -0
- package/crates/enact-core/src/kernel/enforcement.rs +1315 -0
- package/crates/enact-core/src/kernel/error.rs +1200 -0
- package/crates/enact-core/src/kernel/event.rs +1394 -0
- package/crates/enact-core/src/kernel/execution_model.rs +831 -0
- package/crates/enact-core/src/kernel/execution_state.rs +189 -0
- package/crates/enact-core/src/kernel/execution_strategy.rs +117 -0
- package/crates/enact-core/src/kernel/ids.rs +2086 -0
- package/crates/enact-core/src/kernel/interrupt.rs +125 -0
- package/crates/enact-core/src/kernel/kernel.rs +1283 -0
- package/crates/enact-core/src/kernel/mod.rs +205 -0
- package/crates/enact-core/src/kernel/persistence/event_store.rs +270 -0
- package/crates/enact-core/src/kernel/persistence/message_store.rs +908 -0
- package/crates/enact-core/src/kernel/persistence/mod.rs +102 -0
- package/crates/enact-core/src/kernel/persistence/state_store.rs +228 -0
- package/crates/enact-core/src/kernel/persistence/vector_store.rs +299 -0
- package/crates/enact-core/src/kernel/reducer.rs +808 -0
- package/crates/enact-core/src/kernel/replay.rs +153 -0
- package/crates/enact-core/src/lib.rs +413 -0
- package/crates/enact-core/src/memory/episodic.rs +0 -0
- package/crates/enact-core/src/memory/mod.rs +6 -0
- package/crates/enact-core/src/memory/semantic.rs +0 -0
- package/crates/enact-core/src/memory/trait.rs +0 -0
- package/crates/enact-core/src/memory/vector_db.rs +0 -0
- package/crates/enact-core/src/memory/working.rs +0 -0
- package/crates/enact-core/src/policy/execution_policy.rs +292 -0
- package/crates/enact-core/src/policy/filters.rs +458 -0
- package/crates/enact-core/src/policy/input_processor.rs +407 -0
- package/crates/enact-core/src/policy/long_running.rs +134 -0
- package/crates/enact-core/src/policy/mod.rs +193 -0
- package/crates/enact-core/src/policy/pii_input.rs +274 -0
- package/crates/enact-core/src/policy/tenant_policy.rs +453 -0
- package/crates/enact-core/src/policy/tool_policy.rs +407 -0
- package/crates/enact-core/src/providers/mod.rs +63 -0
- package/crates/enact-core/src/providers/trait.rs +292 -0
- package/crates/enact-core/src/runner/callbacks.rs +6 -0
- package/crates/enact-core/src/runner/execution_runner.rs +476 -0
- package/crates/enact-core/src/runner/loop.rs +117 -0
- package/crates/enact-core/src/runner/mod.rs +58 -0
- package/crates/enact-core/src/runner/protected_runner.rs +280 -0
- package/crates/enact-core/src/signal/inmemory.rs +231 -0
- package/crates/enact-core/src/signal/mod.rs +108 -0
- package/crates/enact-core/src/streaming/event_logger.rs +195 -0
- package/crates/enact-core/src/streaming/event_stream.rs +1423 -0
- package/crates/enact-core/src/streaming/mod.rs +108 -0
- package/crates/enact-core/src/streaming/pause_cancel.rs +0 -0
- package/crates/enact-core/src/streaming/protected_emitter.rs +173 -0
- package/crates/enact-core/src/streaming/protection/context.rs +136 -0
- package/crates/enact-core/src/streaming/protection/encryption.rs +289 -0
- package/crates/enact-core/src/streaming/protection/mod.rs +43 -0
- package/crates/enact-core/src/streaming/protection/pii_protection.rs +243 -0
- package/crates/enact-core/src/streaming/protection/processor.rs +166 -0
- package/crates/enact-core/src/streaming/sse.rs +0 -0
- package/crates/enact-core/src/telemetry/exporter.rs +0 -0
- package/crates/enact-core/src/telemetry/init.rs +0 -0
- package/crates/enact-core/src/telemetry/mod.rs +49 -0
- package/crates/enact-core/src/telemetry/spans.rs +245 -0
- package/crates/enact-core/src/tool/agent_tool.rs +177 -0
- package/crates/enact-core/src/tool/browser/mod.rs +0 -0
- package/crates/enact-core/src/tool/browser/webdriver.rs +0 -0
- package/crates/enact-core/src/tool/cost.rs +247 -0
- package/crates/enact-core/src/tool/discovery.rs +0 -0
- package/crates/enact-core/src/tool/dispatcher.rs +347 -0
- package/crates/enact-core/src/tool/filesystem.rs +231 -0
- package/crates/enact-core/src/tool/function.rs +99 -0
- package/crates/enact-core/src/tool/git.rs +162 -0
- package/crates/enact-core/src/tool/http.rs +214 -0
- package/crates/enact-core/src/tool/mcp/client.rs +0 -0
- package/crates/enact-core/src/tool/mcp/mod.rs +0 -0
- package/crates/enact-core/src/tool/mod.rs +51 -0
- package/crates/enact-core/src/tool/reasoning/debugging.rs +0 -0
- package/crates/enact-core/src/tool/reasoning/mcts.rs +0 -0
- package/crates/enact-core/src/tool/reasoning/mod.rs +0 -0
- package/crates/enact-core/src/tool/reasoning/sequential.rs +0 -0
- package/crates/enact-core/src/tool/sandbox/dagger.rs +0 -0
- package/crates/enact-core/src/tool/sandbox/mod.rs +0 -0
- package/crates/enact-core/src/tool/shell.rs +147 -0
- package/crates/enact-core/src/tool/trait.rs +33 -0
- package/crates/enact-core/src/tool/web_search.rs +277 -0
- package/crates/enact-core/src/util/config.rs +0 -0
- package/crates/enact-core/src/util/errors.rs +0 -0
- package/crates/enact-core/src/util/mod.rs +6 -0
- package/crates/enact-core/tests/airgapped_e2e_test.rs +291 -0
- package/crates/enact-core/tests/e2e_agentic_loop.rs +119 -0
- package/crates/enact-core/tests/e2e_test.rs +259 -0
- package/crates/enact-core/tests/graph_test.rs +130 -0
- package/crates/enact-core/tests/stream_event_id_validation.rs +435 -0
- package/crates/enact-cron/Cargo.toml +28 -0
- package/crates/enact-cron/src/lib.rs +44 -0
- package/crates/enact-cron/src/schedule.rs +156 -0
- package/crates/enact-cron/src/store.rs +589 -0
- package/crates/enact-cron/src/types.rs +148 -0
- package/crates/enact-gateway/Cargo.toml +31 -0
- package/crates/enact-gateway/README.md +30 -0
- package/crates/enact-gateway/examples/whatsapp-gateway-runner-mock.rs +59 -0
- package/crates/enact-gateway/examples/whatsapp-gateway.rs +42 -0
- package/crates/enact-gateway/src/lib.rs +582 -0
- package/crates/enact-mcp/Cargo.toml +24 -0
- package/crates/enact-mcp/src/lib.rs +178 -0
- package/crates/enact-memory/Cargo.toml +25 -0
- package/crates/enact-memory/src/backend.rs +20 -0
- package/crates/enact-memory/src/chunker.rs +230 -0
- package/crates/enact-memory/src/embeddings.rs +221 -0
- package/crates/enact-memory/src/lib.rs +67 -0
- package/crates/enact-memory/src/markdown.rs +127 -0
- package/crates/enact-memory/src/none.rs +61 -0
- package/crates/enact-memory/src/sqlite.rs +276 -0
- package/crates/enact-memory/src/traits.rs +65 -0
- package/crates/enact-memory/src/vector.rs +198 -0
- package/crates/enact-oauth/Cargo.toml +27 -0
- package/crates/enact-oauth/src/lib.rs +584 -0
- package/crates/enact-observability/Cargo.toml +22 -0
- package/crates/enact-observability/src/lib.rs +197 -0
- package/crates/enact-providers/Cargo.toml +33 -0
- package/crates/enact-providers/examples/hello-agent.rs +33 -0
- package/crates/enact-providers/src/anthropic.rs +182 -0
- package/crates/enact-providers/src/azure.rs +96 -0
- package/crates/enact-providers/src/bridge.rs +221 -0
- package/crates/enact-providers/src/gemini.rs +227 -0
- package/crates/enact-providers/src/http.rs +78 -0
- package/crates/enact-providers/src/lib.rs +53 -0
- package/crates/enact-providers/src/openai_compatible.rs +167 -0
- package/crates/enact-providers/src/openrouter.rs +33 -0
- package/crates/enact-runner/Cargo.toml +24 -0
- package/crates/enact-runner/README.md +76 -0
- package/crates/enact-runner/src/compaction.rs +225 -0
- package/crates/enact-runner/src/config.rs +118 -0
- package/crates/enact-runner/src/lib.rs +63 -0
- package/crates/enact-runner/src/loop_driver.rs +414 -0
- package/crates/enact-runner/src/parser.rs +421 -0
- package/crates/enact-runner/src/retry.rs +262 -0
- package/crates/enact-runner/tests/integration.rs +278 -0
- package/crates/enact-security/Cargo.toml +22 -0
- package/crates/enact-security/src/audit.rs +375 -0
- package/crates/enact-security/src/lib.rs +37 -0
- package/crates/enact-security/src/policy.rs +406 -0
- package/crates/enact-skills/Cargo.toml +25 -0
- package/crates/enact-skills/src/lib.rs +506 -0
- package/crates/enact-tools/Cargo.toml +22 -0
- package/crates/enact-tools/src/file_read.rs +166 -0
- package/crates/enact-tools/src/file_write.rs +216 -0
- package/crates/enact-tools/src/git_operations.rs +513 -0
- package/crates/enact-tools/src/http_request.rs +417 -0
- package/crates/enact-tools/src/lib.rs +104 -0
- package/crates/enact-tools/src/security.rs +227 -0
- package/crates/enact-tools/src/shell.rs +191 -0
- package/crates/enact-tools/src/traits.rs +159 -0
- package/docs/Makefile +74 -0
- package/docs/config.toml +62 -0
- package/docs/content/_index.md +174 -0
- package/docs/content/a2a/_index.md +431 -0
- package/docs/content/api/_index.md +323 -0
- package/docs/content/channels/_index.md +160 -0
- package/docs/content/channels/teams.md +205 -0
- package/docs/content/channels/telegram.md +182 -0
- package/docs/content/channels/webhook.md +423 -0
- package/docs/content/channels/whatsapp.md +240 -0
- package/docs/content/cli/_index.md +261 -0
- package/docs/content/concepts/_index.md +273 -0
- package/docs/content/configuration/_index.md +241 -0
- package/docs/content/cron/_index.md +248 -0
- package/docs/content/developers/_index.md +278 -0
- package/docs/content/getting-started/_index.md +180 -0
- package/docs/content/installation/_index.md +186 -0
- package/docs/content/installation/uninstall.md +101 -0
- package/docs/content/installation/updating.md +120 -0
- package/docs/content/mcp/_index.md +215 -0
- package/docs/content/memory/_index.md +163 -0
- package/docs/content/oauth/_index.md +515 -0
- package/docs/content/providers/_index.md +206 -0
- package/docs/content/roadmap/_index.md +199 -0
- package/docs/content/security/_index.md +219 -0
- package/docs/content/skills/_index.md +228 -0
- package/docs/content/tools/_index.md +485 -0
- package/docs/content/troubleshooting/_index.md +259 -0
- package/docs/content/yaml-schema/_index.md +294 -0
- package/docs/static/giallo-dark.css +91 -0
- package/docs/static/giallo-light.css +91 -0
- package/docs/themes/tanuki/.github/workflows/deploy.yml +44 -0
- package/docs/themes/tanuki/LICENSE +21 -0
- package/docs/themes/tanuki/README.md +166 -0
- package/docs/themes/tanuki/examples/blog/config.toml +58 -0
- package/docs/themes/tanuki/examples/blog/content/_index.md +4 -0
- package/docs/themes/tanuki/examples/blog/content/about.md +33 -0
- package/docs/themes/tanuki/examples/blog/content/blog/_index.md +7 -0
- package/docs/themes/tanuki/examples/blog/content/blog/api-design-best-practices.md +245 -0
- package/docs/themes/tanuki/examples/blog/content/blog/building-accessible-websites.md +147 -0
- package/docs/themes/tanuki/examples/blog/content/blog/css-grid-vs-flexbox.md +165 -0
- package/docs/themes/tanuki/examples/blog/content/blog/customizing-catppuccin-colors.md +137 -0
- package/docs/themes/tanuki/examples/blog/content/blog/dark-mode-best-practices.md +82 -0
- package/docs/themes/tanuki/examples/blog/content/blog/docker-essentials.md +301 -0
- package/docs/themes/tanuki/examples/blog/content/blog/getting-started-with-zola.md +129 -0
- package/docs/themes/tanuki/examples/blog/content/blog/git-workflow-for-content.md +112 -0
- package/docs/themes/tanuki/examples/blog/content/blog/introduction-to-webassembly.md +183 -0
- package/docs/themes/tanuki/examples/blog/content/blog/modern-javascript-features.md +234 -0
- package/docs/themes/tanuki/examples/blog/content/blog/testing-strategies.md +311 -0
- package/docs/themes/tanuki/examples/blog/content/blog/typography-for-developers.md +104 -0
- package/docs/themes/tanuki/examples/blog/content/blog/welcome-to-tanuki.md +67 -0
- package/docs/themes/tanuki/examples/blog/content/blog/why-static-sites.md +85 -0
- package/docs/themes/tanuki/examples/blog/content/projects.md +64 -0
- package/docs/themes/tanuki/examples/book/config.toml +17 -0
- package/docs/themes/tanuki/examples/book/content/_index.md +12 -0
- package/docs/themes/tanuki/examples/book/content/chapter-1.md +90 -0
- package/docs/themes/tanuki/examples/book/content/chapter-2.md +143 -0
- package/docs/themes/tanuki/examples/book/content/chapter-3.md +217 -0
- package/docs/themes/tanuki/examples/book/content/chapter-4.md +224 -0
- package/docs/themes/tanuki/examples/book/content/chapter-5.md +297 -0
- package/docs/themes/tanuki/examples/book/content/print.md +6 -0
- package/docs/themes/tanuki/examples/docs/config.toml +28 -0
- package/docs/themes/tanuki/examples/docs/content/_index.md +20 -0
- package/docs/themes/tanuki/examples/docs/content/components.md +156 -0
- package/docs/themes/tanuki/examples/docs/content/configuration.md +94 -0
- package/docs/themes/tanuki/examples/docs/content/customization.md +202 -0
- package/docs/themes/tanuki/examples/docs/content/deployment.md +204 -0
- package/docs/themes/tanuki/examples/docs/content/installation.md +59 -0
- package/docs/themes/tanuki/examples/docs/content/print.md +6 -0
- package/docs/themes/tanuki/examples/docs/static/img/tanuki-icon.avif +0 -0
- package/docs/themes/tanuki/examples/index.html +2104 -0
- package/docs/themes/tanuki/mise.toml +108 -0
- package/docs/themes/tanuki/sass/base/_catppuccin.scss +164 -0
- package/docs/themes/tanuki/sass/base/_fonts.scss +64 -0
- package/docs/themes/tanuki/sass/base/_reset.scss +152 -0
- package/docs/themes/tanuki/sass/base/_typography.scss +523 -0
- package/docs/themes/tanuki/sass/components/_buttons.scss +209 -0
- package/docs/themes/tanuki/sass/components/_code.scss +457 -0
- package/docs/themes/tanuki/sass/components/_landing.scss +633 -0
- package/docs/themes/tanuki/sass/components/_layout.scss +294 -0
- package/docs/themes/tanuki/sass/components/_navigation.scss +1200 -0
- package/docs/themes/tanuki/sass/components/_print.scss +237 -0
- package/docs/themes/tanuki/sass/components/_search.scss +224 -0
- package/docs/themes/tanuki/sass/components/_sidebar.scss +473 -0
- package/docs/themes/tanuki/sass/components/_theme-toggle.scss +186 -0
- package/docs/themes/tanuki/sass/modes/_blog.scss +366 -0
- package/docs/themes/tanuki/sass/modes/_product.scss +875 -0
- package/docs/themes/tanuki/sass/modes/_raskell.scss +1696 -0
- package/docs/themes/tanuki/sass/patterns/_buttons.scss +183 -0
- package/docs/themes/tanuki/sass/patterns/_cards.scss +144 -0
- package/docs/themes/tanuki/sass/patterns/_index.scss +9 -0
- package/docs/themes/tanuki/sass/patterns/_lists.scss +259 -0
- package/docs/themes/tanuki/sass/patterns/_sections.scss +243 -0
- package/docs/themes/tanuki/sass/style.scss +47 -0
- package/docs/themes/tanuki/sass/tokens/_colors.scss +139 -0
- package/docs/themes/tanuki/sass/tokens/_spacing.scss +100 -0
- package/docs/themes/tanuki/sass/tokens/_typography.scss +186 -0
- package/docs/themes/tanuki/screenshot.png +0 -0
- package/docs/themes/tanuki/sentinel.kdl +59 -0
- package/docs/themes/tanuki/static/elasticlunr.min.js +10 -0
- package/docs/themes/tanuki/static/fonts/GEIST-LICENSE.txt +92 -0
- package/docs/themes/tanuki/static/fonts/Geist-Variable.woff2 +0 -0
- package/docs/themes/tanuki/static/fonts/GeistMono-Variable.woff2 +0 -0
- package/docs/themes/tanuki/static/img/tanuki-icon.avif +0 -0
- package/docs/themes/tanuki/static/img/tanuki-icon.png +0 -0
- package/docs/themes/tanuki/static/js/anchors.js +18 -0
- package/docs/themes/tanuki/static/js/app.js +274 -0
- package/docs/themes/tanuki/static/js/code.js +394 -0
- package/docs/themes/tanuki/static/js/navigation.js +778 -0
- package/docs/themes/tanuki/static/js/scroll-to-top.js +33 -0
- package/docs/themes/tanuki/static/js/search-raskell.js +240 -0
- package/docs/themes/tanuki/static/js/search.js +215 -0
- package/docs/themes/tanuki/static/js/theme.js +169 -0
- package/docs/themes/tanuki/static/syntax-dark.css +151 -0
- package/docs/themes/tanuki/static/syntax-light.css +151 -0
- package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm.js +486 -0
- package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm_bg.wasm +0 -0
- package/docs/themes/tanuki/templates/404.html +52 -0
- package/docs/themes/tanuki/templates/base.html +428 -0
- package/docs/themes/tanuki/templates/blog.html +66 -0
- package/docs/themes/tanuki/templates/home.html +108 -0
- package/docs/themes/tanuki/templates/index.html +178 -0
- package/docs/themes/tanuki/templates/landing.html +168 -0
- package/docs/themes/tanuki/templates/macros/nav.html +128 -0
- package/docs/themes/tanuki/templates/macros/posts.html +101 -0
- package/docs/themes/tanuki/templates/macros/ui.html +159 -0
- package/docs/themes/tanuki/templates/page.html +135 -0
- package/docs/themes/tanuki/templates/partials/footer.html +38 -0
- package/docs/themes/tanuki/templates/partials/header.html +366 -0
- package/docs/themes/tanuki/templates/partials/nav-buttons.html +55 -0
- package/docs/themes/tanuki/templates/partials/nav-overlay.html +81 -0
- package/docs/themes/tanuki/templates/partials/page-toc-panel.html +43 -0
- package/docs/themes/tanuki/templates/partials/search.html +52 -0
- package/docs/themes/tanuki/templates/partials/sidebar.html +107 -0
- package/docs/themes/tanuki/templates/partials/theme-toggle.html +35 -0
- package/docs/themes/tanuki/templates/partials/toc-overlay.html +146 -0
- package/docs/themes/tanuki/templates/partials/version-picker.html +38 -0
- package/docs/themes/tanuki/templates/print.html +244 -0
- package/docs/themes/tanuki/templates/section.html +186 -0
- package/docs/themes/tanuki/templates/taxonomy_list.html +18 -0
- package/docs/themes/tanuki/templates/taxonomy_single.html +31 -0
- package/docs/themes/tanuki/theme.toml +58 -0
- package/examples/hello-agent.rs +55 -0
- package/package.json +36 -0
- package/proto/config.proto +60 -0
- package/proto/events.proto +0 -0
- package/proto/runtime.proto +215 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
+++
|
|
2
|
+
title = "Modern JavaScript Features You Should Be Using"
|
|
3
|
+
description = "ES2020+ brought powerful features that make JavaScript more expressive and less error-prone. Here are the ones worth adopting."
|
|
4
|
+
date = 2024-12-20
|
|
5
|
+
[taxonomies]
|
|
6
|
+
tags = ["javascript", "es2020", "frontend"]
|
|
7
|
+
+++
|
|
8
|
+
|
|
9
|
+
# Modern JavaScript Features You Should Be Using
|
|
10
|
+
|
|
11
|
+
JavaScript evolves yearly. Here are features from recent versions that genuinely improve code quality.
|
|
12
|
+
|
|
13
|
+
## Optional Chaining (?.)
|
|
14
|
+
|
|
15
|
+
Stop writing defensive chains:
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
// Before
|
|
19
|
+
const city = user && user.address && user.address.city;
|
|
20
|
+
|
|
21
|
+
// After
|
|
22
|
+
const city = user?.address?.city;
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Works with methods and arrays too:
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
const result = obj.method?.();
|
|
29
|
+
const first = arr?.[0];
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Nullish Coalescing (??)
|
|
33
|
+
|
|
34
|
+
Default values that respect `0` and `''`:
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
// Problem with ||
|
|
38
|
+
const count = response.count || 10; // 0 becomes 10!
|
|
39
|
+
|
|
40
|
+
// Solution with ??
|
|
41
|
+
const count = response.count ?? 10; // 0 stays 0
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Logical Assignment Operators
|
|
45
|
+
|
|
46
|
+
Combine logical operators with assignment:
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
// ||= assigns if falsy
|
|
50
|
+
options.timeout ||= 3000;
|
|
51
|
+
|
|
52
|
+
// ??= assigns if nullish
|
|
53
|
+
user.name ??= 'Anonymous';
|
|
54
|
+
|
|
55
|
+
// &&= assigns if truthy
|
|
56
|
+
config.debug &&= validateDebugMode();
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Array Methods
|
|
60
|
+
|
|
61
|
+
### at() - Negative Indexing
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
const arr = [1, 2, 3, 4, 5];
|
|
65
|
+
|
|
66
|
+
// Before
|
|
67
|
+
const last = arr[arr.length - 1];
|
|
68
|
+
|
|
69
|
+
// After
|
|
70
|
+
const last = arr.at(-1);
|
|
71
|
+
const secondLast = arr.at(-2);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### findLast() and findLastIndex()
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
const transactions = [
|
|
78
|
+
{ type: 'credit', amount: 100 },
|
|
79
|
+
{ type: 'debit', amount: 50 },
|
|
80
|
+
{ type: 'credit', amount: 200 },
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
const lastCredit = transactions.findLast(t => t.type === 'credit');
|
|
84
|
+
// { type: 'credit', amount: 200 }
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### toSorted(), toReversed(), toSpliced()
|
|
88
|
+
|
|
89
|
+
Non-mutating array operations:
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
const original = [3, 1, 2];
|
|
93
|
+
|
|
94
|
+
const sorted = original.toSorted();
|
|
95
|
+
// sorted: [1, 2, 3]
|
|
96
|
+
// original: [3, 1, 2] - unchanged!
|
|
97
|
+
|
|
98
|
+
const reversed = original.toReversed();
|
|
99
|
+
const removed = original.toSpliced(1, 1);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Object.groupBy()
|
|
103
|
+
|
|
104
|
+
Native grouping without lodash:
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
const inventory = [
|
|
108
|
+
{ name: 'apples', type: 'fruit' },
|
|
109
|
+
{ name: 'bananas', type: 'fruit' },
|
|
110
|
+
{ name: 'carrots', type: 'vegetable' },
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
const grouped = Object.groupBy(inventory, item => item.type);
|
|
114
|
+
// {
|
|
115
|
+
// fruit: [{ name: 'apples', ... }, { name: 'bananas', ... }],
|
|
116
|
+
// vegetable: [{ name: 'carrots', ... }]
|
|
117
|
+
// }
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Promise.allSettled()
|
|
121
|
+
|
|
122
|
+
Wait for all promises regardless of rejection:
|
|
123
|
+
|
|
124
|
+
```javascript
|
|
125
|
+
const results = await Promise.allSettled([
|
|
126
|
+
fetch('/api/users'),
|
|
127
|
+
fetch('/api/posts'),
|
|
128
|
+
fetch('/api/comments'),
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
results.forEach(result => {
|
|
132
|
+
if (result.status === 'fulfilled') {
|
|
133
|
+
console.log(result.value);
|
|
134
|
+
} else {
|
|
135
|
+
console.error(result.reason);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Private Class Fields
|
|
141
|
+
|
|
142
|
+
True privacy with `#`:
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
class Counter {
|
|
146
|
+
#count = 0;
|
|
147
|
+
|
|
148
|
+
increment() {
|
|
149
|
+
this.#count++;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
get value() {
|
|
153
|
+
return this.#count;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const counter = new Counter();
|
|
158
|
+
counter.increment();
|
|
159
|
+
console.log(counter.value); // 1
|
|
160
|
+
console.log(counter.#count); // SyntaxError!
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Static Blocks
|
|
164
|
+
|
|
165
|
+
Complex static initialization:
|
|
166
|
+
|
|
167
|
+
```javascript
|
|
168
|
+
class Config {
|
|
169
|
+
static values;
|
|
170
|
+
|
|
171
|
+
static {
|
|
172
|
+
try {
|
|
173
|
+
this.values = JSON.parse(localStorage.getItem('config'));
|
|
174
|
+
} catch {
|
|
175
|
+
this.values = { theme: 'dark' };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Top-Level Await
|
|
182
|
+
|
|
183
|
+
Use `await` outside async functions in modules:
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
// config.js
|
|
187
|
+
const response = await fetch('/api/config');
|
|
188
|
+
export const config = await response.json();
|
|
189
|
+
|
|
190
|
+
// main.js
|
|
191
|
+
import { config } from './config.js';
|
|
192
|
+
// config is already resolved
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Temporal API (Coming Soon)
|
|
196
|
+
|
|
197
|
+
Finally, a sane date/time API:
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
// Current (painful)
|
|
201
|
+
const date = new Date();
|
|
202
|
+
date.setDate(date.getDate() + 7);
|
|
203
|
+
|
|
204
|
+
// Temporal (clear)
|
|
205
|
+
const date = Temporal.Now.plainDateISO();
|
|
206
|
+
const nextWeek = date.add({ days: 7 });
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Error Cause
|
|
210
|
+
|
|
211
|
+
Chain errors with context:
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
try {
|
|
215
|
+
await fetchUserData(userId);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
throw new Error('Failed to load user profile', {
|
|
218
|
+
cause: error
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Quick Adoption Guide
|
|
224
|
+
|
|
225
|
+
| Feature | Use When |
|
|
226
|
+
|---------|----------|
|
|
227
|
+
| `?.` | Accessing nested properties |
|
|
228
|
+
| `??` | Providing defaults (especially for 0/'') |
|
|
229
|
+
| `at(-1)` | Accessing from array end |
|
|
230
|
+
| `toSorted()` | Sorting without mutation |
|
|
231
|
+
| `Object.groupBy()` | Categorizing arrays |
|
|
232
|
+
| `#private` | Encapsulating class data |
|
|
233
|
+
|
|
234
|
+
Check [caniuse.com](https://caniuse.com) for browser support before using in production.
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
+++
|
|
2
|
+
title = "Testing Strategies That Actually Work"
|
|
3
|
+
description = "Testing isn't about coverage percentages. It's about confidence. Here's how to test effectively."
|
|
4
|
+
date = 2024-12-26
|
|
5
|
+
[taxonomies]
|
|
6
|
+
tags = ["testing", "quality", "development"]
|
|
7
|
+
+++
|
|
8
|
+
|
|
9
|
+
# Testing Strategies That Actually Work
|
|
10
|
+
|
|
11
|
+
The goal of testing isn't 100% coverage. It's confidence to ship.
|
|
12
|
+
|
|
13
|
+
## The Testing Trophy
|
|
14
|
+
|
|
15
|
+
Forget the pyramid. Think trophy:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
╱╲
|
|
19
|
+
╱ ╲ E2E (few)
|
|
20
|
+
╱────╲
|
|
21
|
+
╱ ╲ Integration (most)
|
|
22
|
+
╱────────╲
|
|
23
|
+
╱ ╲ Unit (some)
|
|
24
|
+
╱────────────╲
|
|
25
|
+
│ Static Types │
|
|
26
|
+
└──────────────┘
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Static analysis** catches the most bugs with the least effort.
|
|
30
|
+
|
|
31
|
+
## Unit Tests
|
|
32
|
+
|
|
33
|
+
Test pure functions and isolated logic:
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
// utils.test.js
|
|
37
|
+
import { formatCurrency, calculateDiscount } from './utils';
|
|
38
|
+
|
|
39
|
+
describe('formatCurrency', () => {
|
|
40
|
+
it('formats positive amounts', () => {
|
|
41
|
+
expect(formatCurrency(1234.5)).toBe('$1,234.50');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('handles zero', () => {
|
|
45
|
+
expect(formatCurrency(0)).toBe('$0.00');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('formats negative amounts', () => {
|
|
49
|
+
expect(formatCurrency(-50)).toBe('-$50.00');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('calculateDiscount', () => {
|
|
54
|
+
it('applies percentage discount', () => {
|
|
55
|
+
expect(calculateDiscount(100, 0.2)).toBe(80);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('never goes below zero', () => {
|
|
59
|
+
expect(calculateDiscount(10, 2)).toBe(0);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### When to Unit Test
|
|
65
|
+
|
|
66
|
+
- Pure functions
|
|
67
|
+
- Complex business logic
|
|
68
|
+
- Utility functions
|
|
69
|
+
- State reducers
|
|
70
|
+
- Data transformations
|
|
71
|
+
|
|
72
|
+
### When NOT to Unit Test
|
|
73
|
+
|
|
74
|
+
- Simple getters/setters
|
|
75
|
+
- Framework code
|
|
76
|
+
- Implementation details
|
|
77
|
+
- One-liner functions
|
|
78
|
+
|
|
79
|
+
## Integration Tests
|
|
80
|
+
|
|
81
|
+
Test how pieces work together:
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
// api.test.js
|
|
85
|
+
import { createServer } from '../server';
|
|
86
|
+
import { db } from '../database';
|
|
87
|
+
|
|
88
|
+
describe('POST /users', () => {
|
|
89
|
+
let server;
|
|
90
|
+
|
|
91
|
+
beforeAll(async () => {
|
|
92
|
+
server = await createServer();
|
|
93
|
+
await db.migrate();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
afterAll(async () => {
|
|
97
|
+
await db.close();
|
|
98
|
+
await server.close();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
beforeEach(async () => {
|
|
102
|
+
await db.truncate('users');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('creates a user and returns 201', async () => {
|
|
106
|
+
const response = await server.inject({
|
|
107
|
+
method: 'POST',
|
|
108
|
+
url: '/users',
|
|
109
|
+
payload: {
|
|
110
|
+
email: 'test@example.com',
|
|
111
|
+
name: 'Test User',
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
expect(response.statusCode).toBe(201);
|
|
116
|
+
expect(response.json()).toMatchObject({
|
|
117
|
+
email: 'test@example.com',
|
|
118
|
+
name: 'Test User',
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Verify database
|
|
122
|
+
const user = await db.query('SELECT * FROM users WHERE email = $1', ['test@example.com']);
|
|
123
|
+
expect(user).toBeDefined();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('returns 400 for invalid email', async () => {
|
|
127
|
+
const response = await server.inject({
|
|
128
|
+
method: 'POST',
|
|
129
|
+
url: '/users',
|
|
130
|
+
payload: {
|
|
131
|
+
email: 'not-an-email',
|
|
132
|
+
name: 'Test',
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
expect(response.statusCode).toBe(400);
|
|
137
|
+
expect(response.json().error.code).toBe('VALIDATION_ERROR');
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Component Tests (Frontend)
|
|
143
|
+
|
|
144
|
+
Test components with realistic interactions:
|
|
145
|
+
|
|
146
|
+
```javascript
|
|
147
|
+
// UserProfile.test.jsx
|
|
148
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
149
|
+
import userEvent from '@testing-library/user-event';
|
|
150
|
+
import { UserProfile } from './UserProfile';
|
|
151
|
+
import { server } from '../mocks/server';
|
|
152
|
+
import { rest } from 'msw';
|
|
153
|
+
|
|
154
|
+
describe('UserProfile', () => {
|
|
155
|
+
it('displays user information', async () => {
|
|
156
|
+
render(<UserProfile userId="123" />);
|
|
157
|
+
|
|
158
|
+
await waitFor(() => {
|
|
159
|
+
expect(screen.getByRole('heading')).toHaveTextContent('Jane Doe');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(screen.getByText('jane@example.com')).toBeInTheDocument();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('handles edit mode', async () => {
|
|
166
|
+
const user = userEvent.setup();
|
|
167
|
+
render(<UserProfile userId="123" />);
|
|
168
|
+
|
|
169
|
+
await user.click(screen.getByRole('button', { name: /edit/i }));
|
|
170
|
+
|
|
171
|
+
const input = screen.getByLabelText(/name/i);
|
|
172
|
+
await user.clear(input);
|
|
173
|
+
await user.type(input, 'Jane Smith');
|
|
174
|
+
await user.click(screen.getByRole('button', { name: /save/i }));
|
|
175
|
+
|
|
176
|
+
await waitFor(() => {
|
|
177
|
+
expect(screen.getByRole('heading')).toHaveTextContent('Jane Smith');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('shows error state', async () => {
|
|
182
|
+
server.use(
|
|
183
|
+
rest.get('/api/users/:id', (req, res, ctx) => {
|
|
184
|
+
return res(ctx.status(500));
|
|
185
|
+
})
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
render(<UserProfile userId="123" />);
|
|
189
|
+
|
|
190
|
+
await waitFor(() => {
|
|
191
|
+
expect(screen.getByRole('alert')).toHaveTextContent(/failed to load/i);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## E2E Tests
|
|
198
|
+
|
|
199
|
+
Test critical user journeys:
|
|
200
|
+
|
|
201
|
+
```javascript
|
|
202
|
+
// checkout.spec.js (Playwright)
|
|
203
|
+
import { test, expect } from '@playwright/test';
|
|
204
|
+
|
|
205
|
+
test.describe('Checkout Flow', () => {
|
|
206
|
+
test('completes purchase successfully', async ({ page }) => {
|
|
207
|
+
// Add item to cart
|
|
208
|
+
await page.goto('/products/widget');
|
|
209
|
+
await page.click('button:has-text("Add to Cart")');
|
|
210
|
+
|
|
211
|
+
// Go to checkout
|
|
212
|
+
await page.click('a:has-text("Checkout")');
|
|
213
|
+
|
|
214
|
+
// Fill shipping
|
|
215
|
+
await page.fill('[name="email"]', 'test@example.com');
|
|
216
|
+
await page.fill('[name="address"]', '123 Main St');
|
|
217
|
+
await page.fill('[name="city"]', 'Portland');
|
|
218
|
+
await page.selectOption('[name="state"]', 'OR');
|
|
219
|
+
await page.fill('[name="zip"]', '97201');
|
|
220
|
+
|
|
221
|
+
// Fill payment (test card)
|
|
222
|
+
await page.fill('[name="cardNumber"]', '4242424242424242');
|
|
223
|
+
await page.fill('[name="expiry"]', '12/25');
|
|
224
|
+
await page.fill('[name="cvc"]', '123');
|
|
225
|
+
|
|
226
|
+
// Submit
|
|
227
|
+
await page.click('button:has-text("Place Order")');
|
|
228
|
+
|
|
229
|
+
// Verify success
|
|
230
|
+
await expect(page.locator('h1')).toHaveText('Order Confirmed');
|
|
231
|
+
await expect(page.locator('.order-number')).toBeVisible();
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### E2E Best Practices
|
|
237
|
+
|
|
238
|
+
- Test happy paths thoroughly
|
|
239
|
+
- Use realistic test data
|
|
240
|
+
- Run in CI on every PR
|
|
241
|
+
- Keep tests independent
|
|
242
|
+
- Use test IDs for stability
|
|
243
|
+
|
|
244
|
+
## What NOT to Test
|
|
245
|
+
|
|
246
|
+
- Third-party libraries
|
|
247
|
+
- Framework internals
|
|
248
|
+
- Private implementation details
|
|
249
|
+
- Simple pass-through code
|
|
250
|
+
- Autogenerated code
|
|
251
|
+
|
|
252
|
+
## Test Data Strategies
|
|
253
|
+
|
|
254
|
+
### Factories
|
|
255
|
+
|
|
256
|
+
```javascript
|
|
257
|
+
// factories/user.js
|
|
258
|
+
import { faker } from '@faker-js/faker';
|
|
259
|
+
|
|
260
|
+
export function createUser(overrides = {}) {
|
|
261
|
+
return {
|
|
262
|
+
id: faker.string.uuid(),
|
|
263
|
+
email: faker.internet.email(),
|
|
264
|
+
name: faker.person.fullName(),
|
|
265
|
+
createdAt: faker.date.past(),
|
|
266
|
+
...overrides,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// In tests
|
|
271
|
+
const user = createUser({ email: 'specific@test.com' });
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Fixtures
|
|
275
|
+
|
|
276
|
+
```javascript
|
|
277
|
+
// fixtures/users.json
|
|
278
|
+
{
|
|
279
|
+
"admin": {
|
|
280
|
+
"id": "admin-001",
|
|
281
|
+
"email": "admin@example.com",
|
|
282
|
+
"role": "admin"
|
|
283
|
+
},
|
|
284
|
+
"regular": {
|
|
285
|
+
"id": "user-001",
|
|
286
|
+
"email": "user@example.com",
|
|
287
|
+
"role": "user"
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## Measuring Test Quality
|
|
293
|
+
|
|
294
|
+
Coverage is a **floor**, not a **ceiling**.
|
|
295
|
+
|
|
296
|
+
Better metrics:
|
|
297
|
+
- **Mutation score**: Do tests catch bugs?
|
|
298
|
+
- **Flakiness rate**: Are tests reliable?
|
|
299
|
+
- **Time to run**: Are tests fast enough?
|
|
300
|
+
- **Maintenance cost**: Are tests easy to update?
|
|
301
|
+
|
|
302
|
+
## Quick Guidelines
|
|
303
|
+
|
|
304
|
+
| Test Type | Speed | Confidence | Quantity |
|
|
305
|
+
|-----------|-------|------------|----------|
|
|
306
|
+
| Static | Instant | Medium | All code |
|
|
307
|
+
| Unit | Fast | Low-Medium | Some |
|
|
308
|
+
| Integration | Medium | High | Most |
|
|
309
|
+
| E2E | Slow | Highest | Few |
|
|
310
|
+
|
|
311
|
+
Write tests that give you confidence to ship, not tests that give you coverage badges.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
+++
|
|
2
|
+
title = "Typography Fundamentals for Developers"
|
|
3
|
+
description = "Good typography makes your content readable and your site professional. Here are the basics every developer should know."
|
|
4
|
+
date = 2024-12-15
|
|
5
|
+
[taxonomies]
|
|
6
|
+
tags = ["design", "typography", "css"]
|
|
7
|
+
+++
|
|
8
|
+
|
|
9
|
+
# Typography Fundamentals for Developers
|
|
10
|
+
|
|
11
|
+
You don't need to be a designer to get typography right. These fundamentals will take you far.
|
|
12
|
+
|
|
13
|
+
## Choose Readable Fonts
|
|
14
|
+
|
|
15
|
+
For body text, prioritize readability:
|
|
16
|
+
|
|
17
|
+
- **Sans-serif** for screens: Geist, Inter, system-ui
|
|
18
|
+
- **Serif** for long-form: Georgia, Charter, Literata
|
|
19
|
+
- **Monospace** for code: Geist Mono, JetBrains Mono, Fira Code
|
|
20
|
+
|
|
21
|
+
Avoid decorative fonts for body text. Save them for headlines.
|
|
22
|
+
|
|
23
|
+
## Set a Comfortable Line Length
|
|
24
|
+
|
|
25
|
+
Lines that are too long or too short hurt readability. Aim for **45-75 characters** per line:
|
|
26
|
+
|
|
27
|
+
```css
|
|
28
|
+
.prose {
|
|
29
|
+
max-width: 65ch;
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The `ch` unit is based on the width of the "0" character, making it perfect for this.
|
|
34
|
+
|
|
35
|
+
## Use a Type Scale
|
|
36
|
+
|
|
37
|
+
Don't pick font sizes randomly. Use a scale:
|
|
38
|
+
|
|
39
|
+
```css
|
|
40
|
+
:root {
|
|
41
|
+
--text-xs: 0.75rem; /* 12px */
|
|
42
|
+
--text-sm: 0.875rem; /* 14px */
|
|
43
|
+
--text-base: 1rem; /* 16px */
|
|
44
|
+
--text-lg: 1.125rem; /* 18px */
|
|
45
|
+
--text-xl: 1.25rem; /* 20px */
|
|
46
|
+
--text-2xl: 1.5rem; /* 24px */
|
|
47
|
+
--text-3xl: 1.875rem; /* 30px */
|
|
48
|
+
--text-4xl: 2.25rem; /* 36px */
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Line Height Matters
|
|
53
|
+
|
|
54
|
+
Tighter line height for headlines, looser for body text:
|
|
55
|
+
|
|
56
|
+
```css
|
|
57
|
+
h1, h2, h3 {
|
|
58
|
+
line-height: 1.2;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
p {
|
|
62
|
+
line-height: 1.6;
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Vertical Rhythm
|
|
67
|
+
|
|
68
|
+
Consistent spacing creates visual harmony:
|
|
69
|
+
|
|
70
|
+
```css
|
|
71
|
+
.prose > * + * {
|
|
72
|
+
margin-top: 1.5rem;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.prose h2 {
|
|
76
|
+
margin-top: 3rem;
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Font Loading
|
|
81
|
+
|
|
82
|
+
Use `font-display: swap` to prevent invisible text:
|
|
83
|
+
|
|
84
|
+
```css
|
|
85
|
+
@font-face {
|
|
86
|
+
font-family: 'Geist';
|
|
87
|
+
src: url('/fonts/Geist-Variable.woff2') format('woff2');
|
|
88
|
+
font-display: swap;
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Responsive Typography
|
|
93
|
+
|
|
94
|
+
Scale fonts based on viewport:
|
|
95
|
+
|
|
96
|
+
```css
|
|
97
|
+
html {
|
|
98
|
+
font-size: clamp(16px, 1vw + 14px, 20px);
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
This creates fluid typography that adapts to screen size.
|
|
103
|
+
|
|
104
|
+
Good typography is invisible—readers don't notice it, they just enjoy the content.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
+++
|
|
2
|
+
title = "Welcome to Tanuki"
|
|
3
|
+
description = "Introducing Tanuki, a whimsical Zola theme inspired by Nintendo's playful design philosophy."
|
|
4
|
+
date = 2024-12-27
|
|
5
|
+
[taxonomies]
|
|
6
|
+
tags = ["announcement", "release"]
|
|
7
|
+
+++
|
|
8
|
+
|
|
9
|
+
# Welcome to Tanuki
|
|
10
|
+
|
|
11
|
+
We're excited to introduce Tanuki, a new Zola theme that brings joy and whimsy to your documentation, books, and blogs.
|
|
12
|
+
|
|
13
|
+
## Why Tanuki?
|
|
14
|
+
|
|
15
|
+
The tanuki (Japanese raccoon dog) is a beloved creature in Japanese folklore, known for being playful, mischievous, and a little magical. We chose this name because our theme embodies those same qualities—it's fun to use, delightful to look at, and just a bit magical in how it transforms your content.
|
|
16
|
+
|
|
17
|
+
## Three Modes, One Theme
|
|
18
|
+
|
|
19
|
+
Tanuki supports three distinct modes:
|
|
20
|
+
|
|
21
|
+
1. **Documentation Mode** - Perfect for API docs and technical references
|
|
22
|
+
2. **E-Book Mode** - Ideal for tutorials, courses, and long-form content
|
|
23
|
+
3. **Blog Mode** - Great for personal sites, portfolios, and announcements
|
|
24
|
+
|
|
25
|
+
Each mode is optimized for its purpose while maintaining a consistent, cohesive design language.
|
|
26
|
+
|
|
27
|
+
## Features You'll Love
|
|
28
|
+
|
|
29
|
+
### Catppuccin Colors
|
|
30
|
+
|
|
31
|
+
We use the beautiful [Catppuccin](https://github.com/catppuccin/catppuccin) color palette, with Mocha for dark mode and Latte for light mode. The colors are warm, inviting, and easy on the eyes during long reading sessions.
|
|
32
|
+
|
|
33
|
+
### Nintendo-Inspired UX
|
|
34
|
+
|
|
35
|
+
Inspired by games like Animal Crossing and Super Mario Odyssey, we've added subtle animations and interactions that make using the theme feel delightful. Watch the theme toggle sparkle when you click it!
|
|
36
|
+
|
|
37
|
+
### Full-Text Search
|
|
38
|
+
|
|
39
|
+
Press `/` anywhere to search your entire site instantly. Powered by Elasticlunr, it's fast and works offline.
|
|
40
|
+
|
|
41
|
+
## Getting Started
|
|
42
|
+
|
|
43
|
+
Adding Tanuki to your project is simple:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
git submodule add https://github.com/raskell-io/tanuki themes/tanuki
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Then add to your `config.toml`:
|
|
50
|
+
|
|
51
|
+
```toml
|
|
52
|
+
theme = "tanuki"
|
|
53
|
+
|
|
54
|
+
[extra]
|
|
55
|
+
mode = "site" # or "docs" or "book"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## What's Next?
|
|
59
|
+
|
|
60
|
+
We're just getting started! Upcoming features include:
|
|
61
|
+
|
|
62
|
+
- More theme color options
|
|
63
|
+
- Additional page templates
|
|
64
|
+
- Enhanced print stylesheets
|
|
65
|
+
- Improved accessibility features
|
|
66
|
+
|
|
67
|
+
Stay tuned for more updates, and happy building!
|