diff --git a/.env.example b/.env.example index 783e4d1..da8a33b 100644 --- a/.env.example +++ b/.env.example @@ -3,4 +3,4 @@ # Keep all client-exposed variables prefixed with VITE_. VITE_APP_NAME=Forge -VITE_APP_URL=https://forge-orcace.duckdns.org +VITE_APP_URL=https://forge.cuthanhcam.workers.dev diff --git a/README.md b/README.md index 4f11545..52153da 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,92 @@ # Forge -``` -███████╗ ██████╗ ██████╗ ██████╗ ███████╗ -██╔════╝██╔═══██╗██╔══██╗██╔════╝ ██╔════╝ -█████╗ ██║ ██║██████╔╝██║ ███╗█████╗ -██╔══╝ ██║ ██║██╔══██╗██║ ██║██╔══╝ -██║ ╚██████╔╝██║ ██║╚██████╔╝███████╗ -╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ -``` +Forge is a local-first developer workstation for the small but constant tasks +that happen between writing code and shipping it. + +Use it to preview Markdown and HTML, compare text, format JSON, inspect JWTs, +encode data, generate secrets, test regular expressions, and clean up everyday +developer input without jumping across unrelated tabs. + +![Forge home screen](docs/assets/forge-home.png) + +## Why Forge Exists + +Developers often handle sensitive, temporary, messy text: + +- JWTs copied from logs +- JSON payloads from APIs +- YAML configuration snippets +- URLs that need decoding +- secrets that need generating +- text that needs comparing before a commit +- timestamps, UUIDs, slugs, hashes, and regex checks + +Forge keeps these tasks in one calm interface with consistent controls, +keyboard-friendly navigation, and a privacy-first model. + +## Highlights + +- **Local-first workflows** + Core tools run in the browser. Sensitive input stays close to your device. + +- **One product language** + Tools share the same layout patterns, copy actions, toolbar behavior, + keyboard shortcuts, and visual system. + +- **Fast command access** + Open the command palette with `Ctrl K` and jump directly to a tool. + +- **Light, dark, and system themes** + The dark theme is tuned for a soft Forge palette rather than pure black. + +- **Useful by default** + Tools include realistic examples, validation states, copy/export actions, and + workspace persistence where it helps. + +## Tools -Forge is a fast, privacy-first developer toolbox. +### Editors -The project is designed as a browser-based developer workstation: tools are -registered once, discovered through the registry, and surfaced consistently in -navigation, search, routing, favorites, and recent tools. +- Markdown Preview +- HTML Preview +- Diff Checker -## Status +### Data -Forge is in early foundation work. The architecture, contributor workflow, and -tool conventions are being established before the first stable release. +- JSON Formatter +- JSON YAML Converter +- JWT Decoder -## Features +### Encoding -- Browser-only developer tools with no backend requirement for core workflows -- Registry-driven tool model -- Keyboard-first UX and command palette direction -- Persistent workspace primitives -- Privacy-first local execution +- Base64 +- URL Encoder -## Getting Started +### Crypto + +- JWT Secret Generator +- Hash Generator +- Password Generator + +### Utilities + +- UUID Generator +- Timestamp Converter +- Case Converter +- Slugify +- Regex Tester + +## Documentation + +- [Documentation home](docs/README.md) +- [Product overview](docs/product.md) +- [Tool guide](docs/tools.md) +- [Privacy model](docs/privacy.md) +- [Keyboard shortcuts](docs/shortcuts.md) +- [Deployment notes](docs/deployment.md) +- [Support](docs/support.md) + +## Local Development Requirements: @@ -47,34 +105,39 @@ Start the development server: pnpm dev ``` -Run quality checks: +Run the quality gate: ```bash pnpm check ``` -## Documentation +Build for production: + +```bash +pnpm build +``` + +## Deployment + +Forge is a static Vite app. Any static host can serve it after `pnpm build`. + +For single-page app routing, configure the host to fall back every route to +`index.html`. This keeps routes such as `/tools/jwt-decoder`, `/privacy`, and +`/terms` working after refresh. -- [Architecture](docs/architecture.md) -- [Contributing](docs/contributing.md) -- [Adding a new tool](docs/adding-a-new-tool.md) -- [Coding conventions](docs/coding-conventions.md) -- [Roadmap](docs/roadmap.md) +## Privacy -## Project Principles +Forge is designed for sensitive developer workflows. The core tools process +input locally in the browser and do not require an account or backend service. -- Registry first -- Feature isolation -- Shared UI primitives -- Strict TypeScript -- No direct `localStorage` access from feature modules -- Tools run entirely in the browser whenever possible +Read more in [docs/privacy.md](docs/privacy.md). -## Contributing +## Support -Please read [docs/contributing.md](docs/contributing.md) before opening a pull -request. New tools should follow [docs/adding-a-new-tool.md](docs/adding-a-new-tool.md). +- Ask a question: [docs/support.md](docs/support.md) +- Report an issue: [github.com/orcace/forge/issues](https://github.com/orcace/forge/issues) +- Share feedback: [docs/support.md](docs/support.md) ## License -MIT License +MIT License. See [LICENSE](LICENSE). diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..e218695 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,26 @@ +# Forge Documentation + +Welcome to the public documentation for Forge. + +Forge is a browser-based developer workstation for everyday text, data, +encoding, crypto, and utility workflows. It is built around a local-first model +so developers can work with sensitive snippets without sending them through a +collection of unrelated websites. + +## Start Here + +- [Product overview](product.md) +- [Tool guide](tools.md) +- [Privacy model](privacy.md) +- [Keyboard shortcuts](shortcuts.md) +- [Deployment notes](deployment.md) +- [Support](support.md) + +## Documentation Goals + +These docs are written for people using Forge, evaluating Forge, or deploying +their own copy. They explain what the product does, what data it handles, and +how to use it safely. + +Development and implementation notes can live elsewhere when needed; this +folder should stay focused on Forge as a product. diff --git a/docs/adding-a-new-tool.md b/docs/adding-a-new-tool.md deleted file mode 100644 index 85c6163..0000000 --- a/docs/adding-a-new-tool.md +++ /dev/null @@ -1,147 +0,0 @@ -# Adding A New Tool - -This guide describes the expected shape of a Forge tool. - -## 1. Choose A Category - -Allowed categories: - -- Workspace -- Editors -- Data -- Encoding -- Crypto -- Utilities - -Add a new category only when the existing categories cannot describe the tool -without ambiguity. - -## 2. Create The Feature Folder - -Use kebab-case for folders: - -```text -src/features/json-formatter/ -``` - -Recommended files: - -```text -JsonFormatterPage.tsx -json-formatter.service.ts -json-formatter.schema.ts -index.ts -``` - -Use a schema file when the tool accepts external input, reads persisted data, or -imports files. - -## 3. Implement Service Logic First - -Keep parsing, formatting, validation, conversion, and generation logic outside -the page component. - -Good: - -```text -JsonFormatterPage.tsx -> json-formatter.service.ts -``` - -Avoid: - -```text -JsonFormatterPage.tsx contains parser, formatter, storage, and UI logic -``` - -## 4. Validate Inputs - -Use Zod for external data: - -- User-pasted structured data -- Imported files -- URL parameters -- Storage payloads - -Return typed results that the UI can render without guessing. - -## 5. Register The Tool - -Each tool exports metadata compatible with the registry. - -```ts -export const jsonFormatterTool = { - id: "json-formatter", - name: "JSON Formatter", - category: "Data", - description: "Format and validate JSON.", - keywords: ["json", "format", "validate"], - persist: true, - icon: Braces, - component: lazy(() => import("./JsonFormatterPage")), -}; -``` - -Then add the definition to: - -```text -src/core/registry/tool.registry.ts -``` - -The registry should be the only place that needs to know the new tool exists. - -## 6. Persistence Rules - -Use `persist: true` when restoring user input is useful: - -- Markdown preview -- HTML preview -- Diff checker -- JSON formatter - -Use `persist: false` when output is disposable: - -- UUID generator -- Timestamp converter -- Hash generator -- Password generator - -Persistent tools must use the storage service. Do not access `localStorage` -inside feature components. - -## 7. UI Rules - -- Use `ToolLayout` for tool pages. -- Use shared UI primitives from `src/shared/ui`. -- Use Lucide icons. -- Keep controls keyboard accessible. -- Keep generated output copyable. -- Show validation errors close to the relevant input. - -## 8. Tests - -Required for: - -- Service logic -- Schema validation -- Registry integration - -Recommended for: - -- Complex keyboard behavior -- Import/export behavior -- Persistence behavior - -## Done Checklist - -- Feature folder follows naming conventions -- Tool service is separated from UI -- Inputs are validated with Zod where needed -- Tool is registered through the registry -- No sidebar/search/router hardcoding was added -- Tests cover service or core behavior -- `pnpm check` passes -- `pnpm format:check` passes - -## Implementation Note 2 - -Milestone 2 keeps Forge aligned with the registry-first architecture and white-first interface direction. diff --git a/docs/architecture.md b/docs/architecture.md deleted file mode 100644 index 32f9406..0000000 --- a/docs/architecture.md +++ /dev/null @@ -1,131 +0,0 @@ -# Architecture - -Forge is a browser-based developer workstation. It is not a set of isolated -utility pages; each tool is treated as a module that plugs into a shared -workspace shell. - -## Goals - -- Keep tool behavior predictable through a single registry. -- Keep feature modules isolated so tools can be added without editing unrelated - UI surfaces. -- Keep user data local by default. -- Make core systems testable outside React components. - -## Stack - -- React 19 for the UI -- TypeScript in strict mode -- Vite for development and production builds -- Tailwind CSS 4 and shadcn/ui conventions for styling -- Zustand for workspace state -- React Router for navigation -- Zod for validating user, storage, and import data -- Monaco Editor for editor-heavy tools -- Lucide React for icons - -## Layers - -```text -src/app Application entry, providers, global styles -src/layouts Shared page and tool layouts -src/core Product engines and cross-tool state -src/features Isolated developer tools -src/widgets Composed shell UI such as sidebar and command palette -src/shared UI primitives, hooks, utilities, and types -``` - -## Dependency Rules - -Allowed: - -```text -app -> layouts -> widgets -> core -> shared -features -> core -features -> shared -widgets -> core -widgets -> shared -``` - -Forbidden: - -```text -core -> features -feature A -> feature B -shared -> app/core/features/widgets -``` - -These rules keep the registry as the integration point instead of allowing -hidden coupling between tools. - -## Core Systems - -### Registry - -The registry is the source of truth for available tools. Sidebar, routing, -search, command palette, favorites, and recent tools must read registry data -instead of maintaining separate tool lists. - -### Storage - -Features must not call `localStorage` directly. Storage access goes through the -storage service so schema validation, migrations, and key naming stay -centralized. - -### Workspace - -Workspace state contains cross-tool UI state such as active tool, favorites, -recent tools, sidebar state, and user preferences. Domain logic belongs in the -store or service layer, not inline in components. - -### Search - -Search is registry-driven. It indexes tool name, category, description, and -keywords. Search results should not depend on hardcoded page lists. - -## Tool Contract - -A tool module owns its page, service, schema, and public metadata. A tool should -export a `ToolDefinition` that includes: - -```ts -export interface ToolDefinition { - id: string; - name: string; - category: ToolCategory; - description: string; - keywords: string[]; - icon: LucideIcon; - persist: boolean; - component: LazyExoticComponent; -} -``` - -## Testing Strategy - -Required: - -- Registry behavior -- Storage service and migrations -- Search indexing and ranking -- Utility and service logic - -Recommended: - -- Tool service behavior -- Accessibility checks for complex interactions -- Smoke tests for shell-level rendering - -Not required by default: - -- Thin UI wrappers with no logic - -## Non-Goals - -- Forge does not require a backend for core functionality. -- Forge does not aim to become a full IDE. -- Forge does not store user data externally by default. - -## Implementation Note 1 - -Milestone 1 keeps Forge aligned with the registry-first architecture and white-first interface direction. diff --git a/docs/assets/forge-home.png b/docs/assets/forge-home.png new file mode 100644 index 0000000..232a2ad Binary files /dev/null and b/docs/assets/forge-home.png differ diff --git a/docs/coding-conventions.md b/docs/coding-conventions.md deleted file mode 100644 index 6b04b1e..0000000 --- a/docs/coding-conventions.md +++ /dev/null @@ -1,91 +0,0 @@ -# Coding Conventions - -Forge optimizes for readable, maintainable code. Consistency is more important -than personal style. - -## TypeScript - -- Keep `strict` mode enabled. -- Do not use `any`; prefer `unknown` and validate with Zod. -- Use interfaces for object contracts. -- Use type aliases for unions and mapped types. -- Exported functions should have explicit return types. -- Prefer named exports. - -Good: - -```ts -export function formatJson(input: string): string { - return JSON.stringify(JSON.parse(input), null, 2); -} -``` - -Avoid: - -```ts -export default function f(value: any) { - return JSON.stringify(JSON.parse(value), null, 2); -} -``` - -## React - -- Components use PascalCase. -- Keep one primary component per file. -- Keep components focused; extract hooks or children when files grow too large. -- Put business logic in services, stores, or hooks instead of page components. -- Use named exports. - -## State - -- Use one Zustand store per domain. -- Keep domain actions inside the store. -- Components should call actions rather than reconstructing state transitions. - -## Storage - -- Feature modules must not call `localStorage` directly. -- Use the storage service for saving, loading, validation, and migrations. -- Validate persisted payloads before using them. - -## Styling - -- Use Tailwind CSS utilities and shared shadcn/ui-style components. -- Avoid one-off CSS unless the styling is global or cannot be expressed cleanly - with utilities. -- Keep page layouts responsive by default. -- Use stable dimensions for controls that should not shift during interaction. - -## Icons - -- Use `lucide-react`. -- Do not introduce another icon library without approval. - -## Errors - -- Do not silently swallow errors. -- Report errors through the shared logger or visible UI state. -- Use typed result objects for expected validation failures. - -## Tests - -Prioritize tests for: - -- Core services -- Registry behavior -- Search behavior -- Storage migrations -- Tool service logic - -UI smoke tests are useful for shell-level confidence, but service tests should -carry most behavior coverage. - -## Imports - -- Use `@/` for imports from `src`. -- Do not import from another feature module. -- Keep `src/shared` independent from app, core, widgets, and features. - -## Implementation Note 3 - -Milestone 3 keeps Forge aligned with the registry-first architecture and white-first interface direction. diff --git a/docs/contributing.md b/docs/contributing.md deleted file mode 100644 index 77fa0da..0000000 --- a/docs/contributing.md +++ /dev/null @@ -1,96 +0,0 @@ -# Contributing - -Thanks for helping improve Forge. This document defines the workflow expected -for issues, pull requests, and local development. - -## Requirements - -- Node.js 22 or newer -- pnpm 10 or newer - -## Local Setup - -```bash -pnpm install -pnpm dev -``` - -Run the full local quality gate before opening a pull request: - -```bash -pnpm check -pnpm format:check -``` - -## Branch Names - -Use short, descriptive branch names: - -```text -feat/json-formatter -fix/sidebar-collapse -docs/tool-guide -refactor/storage-service -test/registry -``` - -## Commit Messages - -Use Conventional Commits: - -```text -type(scope): summary -``` - -Examples: - -```text -feat(json): add formatter service -fix(storage): validate persisted tool state -docs(architecture): document registry boundaries -test(search): cover keyword matching -``` - -Common types: - -- `feat` -- `fix` -- `docs` -- `style` -- `refactor` -- `test` -- `build` -- `ci` -- `chore` - -## Pull Request Checklist - -Every PR should: - -- Pass `pnpm check` -- Pass `pnpm format:check` -- Include tests for core, service, or utility changes -- Update docs when architecture or contribution behavior changes -- Include screenshots for visible UI changes -- Keep feature modules isolated -- Avoid direct `localStorage` access from `src/features` - -## Review Standards - -Maintainers review for: - -- Type safety -- Clear ownership boundaries -- Registry-driven integration -- Accessible and responsive UI -- Minimal duplication -- Focused changes with no unrelated refactors - -## Adding Tools - -New tools must follow `docs/adding-a-new-tool.md`. Do not add tool-specific -entries directly to sidebar, search, router, favorites, or recent tools. - -## Implementation Note 4 - -Milestone 4 keeps Forge aligned with the registry-first architecture and white-first interface direction. diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..b54d86d --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,63 @@ +# Deployment Notes + +Forge is a static Vite application. + +## Build + +```bash +pnpm install +pnpm build +``` + +The production output is written to `dist/`. + +## Static Hosting + +Forge can be hosted on static platforms such as Cloudflare Pages, Netlify, +Vercel, GitHub Pages, Azure Static Web Apps, or any web server that can serve +the built files. + +## SPA Fallback + +Forge uses client-side routing. Configure your host so unknown paths fall back +to `index.html`. + +This is required for routes such as: + +- `/tools/json-formatter` +- `/tools/jwt-decoder` +- `/privacy` +- `/terms` + +## Environment Variables + +Client-exposed values should use the `VITE_` prefix. + +Example: + +```env +VITE_APP_NAME=Forge +VITE_APP_URL=https://your-forge-domain.example +``` + +The app should avoid hardcoding temporary deployment domains. Put deployment +URLs in environment files or host configuration instead. + +## Cloudflare Pages + +Recommended defaults: + +- Build command: `pnpm build` +- Output directory: `dist` +- Node.js version: 22 or newer +- Package manager: pnpm 10 or newer + +Forge includes `public/_redirects`, which Vite copies into `dist/`. Cloudflare +Pages uses this file to serve `index.html` for client-side routes: + +```text +/* /index.html 200 +``` + +If route refreshes return 404 on another host, add the equivalent SPA fallback +rule in that platform's configuration. diff --git a/docs/privacy.md b/docs/privacy.md new file mode 100644 index 0000000..1948d52 --- /dev/null +++ b/docs/privacy.md @@ -0,0 +1,53 @@ +# Privacy Model + +Forge is designed around sensitive developer text. + +Developers often paste JWTs, JSON payloads, snippets, generated secrets, +encoded URLs, and debugging output into utilities. Forge keeps those workflows +local-first whenever possible. + +## Local Processing + +Core tools run in the browser. Input is processed by client-side JavaScript +instead of being sent to a Forge backend. + +Examples: + +- JSON formatting +- YAML conversion +- JWT decoding +- Base64 and URL encoding +- hashing +- password and secret generation +- regex testing +- text diffing + +## Browser Storage + +Forge may store local workspace preferences in your browser: + +- selected theme +- sidebar state +- recent tools +- persisted tool drafts where enabled + +This storage is local to the browser profile and device. + +## External Links + +Some support flows open external services such as GitHub, email, or LinkedIn. +Do not paste private tokens, customer data, credentials, or proprietary source +code into public support channels. + +## Self-hosting + +If you deploy Forge yourself, your hosting provider, CDN, reverse proxy, +extensions, analytics, and browser environment may affect privacy. Review your +own deployment path before using it with sensitive data. + +## Practical Safety Checklist + +- Prefer a trusted local or self-hosted deployment for production secrets. +- Redact sensitive values before reporting bugs. +- Rotate any credential that was pasted into an untrusted environment. +- Validate generated secrets and passwords against your own policy. diff --git a/docs/product.md b/docs/product.md new file mode 100644 index 0000000..c91df41 --- /dev/null +++ b/docs/product.md @@ -0,0 +1,50 @@ +# Product Overview + +Forge is a local-first toolbox for developer workflows that are too small for a +full application but too important for random paste-and-copy websites. + +It brings common utilities into one interface: + +- editors and previews +- diff review +- structured data formatting +- encoders and decoders +- token inspection +- secret and password generation +- timestamp, UUID, case, slug, and regex helpers + +## Product Principles + +### Local-first + +Forge should process sensitive input in the browser whenever the feature can be +implemented locally. + +### One workspace + +Each tool should feel like part of the same product. Toolbars, pane headers, +copy actions, validation states, and keyboard shortcuts use a consistent +language. + +### Quiet interface + +Forge avoids loud marketing layouts inside tools. Tool screens prioritize dense, +readable panes, clear controls, stable spacing, and predictable action +placement. + +### Fast recovery + +Tools should make it easy to paste, inspect, copy, clear, export, or generate +sample input without interrupting the current workflow. + +## Who Forge Is For + +Forge is built for developers, QA engineers, support engineers, security-minded +reviewers, and anyone who repeatedly handles structured or encoded text during +technical work. + +## What Forge Is Not + +Forge is not a backend service, account system, security audit platform, or +replacement for production validation. Always verify generated output before +using it in critical systems. diff --git a/docs/roadmap.md b/docs/roadmap.md deleted file mode 100644 index 340a875..0000000 --- a/docs/roadmap.md +++ /dev/null @@ -1,126 +0,0 @@ -# Roadmap - -Forge is being built in small milestones. The priority is a stable workstation -architecture before expanding the tool catalog. - -## v0.1 Foundation - -Status: In progress - -Goals: - -- Vite, React, TypeScript, Tailwind, and shadcn/ui-style foundation -- Strict linting, formatting, testing, and CI -- App shell -- Registry contract -- Basic documentation for contributors - -Exit criteria: - -- `pnpm check` passes in CI -- `pnpm format:check` passes in CI or locally before PRs -- Tool registry contract is implemented -- First tool can be registered without shell changes - -## v0.2 Workspace Shell - -Goals: - -- App layout -- Sidebar -- Header -- Tool layout -- Theme provider -- Favorites and recent tools - -Exit criteria: - -- Navigation is registry-driven -- Theme preference persists -- Sidebar and command surfaces read from the registry - -## v0.3 Editors - -Tools: - -- Markdown Preview -- HTML Preview -- Diff Checker - -Focus: - -- Monaco integration -- Persistent editor state -- Copy/export actions -- Validation and error states - -## v0.4 Data Tools - -Tools: - -- JSON Formatter -- JSON Validator -- JSON to YAML -- YAML to JSON -- JWT Decoder - -Focus: - -- Structured validation -- Clear error messages -- Copyable output -- Service-level tests - -## v0.5 Utilities - -Tools: - -- UUID Generator -- Timestamp Converter -- Case Converter -- Slugify -- Base64 Encode/Decode -- URL Encode/Decode - -Focus: - -- Fast single-screen workflows -- Consistent action buttons -- Shared input/output patterns - -## v0.6 Crypto - -Tools: - -- SHA-256 -- SHA-512 -- HMAC Generator -- Password Generator - -Focus: - -- Browser-native crypto APIs where possible -- Clear security limitations -- No secret persistence by default - -## v1.0 Stable - -Requirements: - -- Stable architecture -- Complete core documentation -- Tests for registry, storage, search, and utilities -- Accessibility review -- Responsive shell and tools -- Public contribution workflow - -## Non-Goals - -- Backend dependency for core workflows -- Account system -- Cloud sync by default -- Replacing a full IDE - -## Implementation Note 5 - -Milestone 5 keeps Forge aligned with the registry-first architecture and white-first interface direction. diff --git a/docs/shortcuts.md b/docs/shortcuts.md new file mode 100644 index 0000000..713f2bb --- /dev/null +++ b/docs/shortcuts.md @@ -0,0 +1,31 @@ +# Keyboard Shortcuts + +Forge keeps keyboard behavior small and predictable. + +## General + +| Shortcut | Action | +| -------- | --------------------------------- | +| `Ctrl K` | Open command palette | +| `Esc` | Close dialogs, menus, and flyouts | + +## Command Palette + +| Shortcut | Action | +| -------- | ----------------------- | +| `Up` | Move to previous result | +| `Down` | Move to next result | +| `Enter` | Open selected result | + +## Editors + +| Shortcut | Action | +| -------- | ---------------------------- | +| `Ctrl A` | Select editor content | +| `Ctrl C` | Copy selected content | +| `Tab` | Indent in JSON-style editors | + +## Notes + +Browser and operating system shortcuts still apply. If a shortcut conflicts +with your environment, the browser may handle it first. diff --git a/docs/support.md b/docs/support.md new file mode 100644 index 0000000..7e85173 --- /dev/null +++ b/docs/support.md @@ -0,0 +1,34 @@ +# Support + +Forge is maintained as an open developer tool. + +## Ask A Question + +Use the in-app support page from your Forge workspace, or send a direct email +to `cuthanhcam04@gmail.com`. + +## Report An Issue + +Use GitHub Issues for bugs, broken UI states, incorrect output, and feature +requests: + +[https://github.com/orcace/forge/issues](https://github.com/orcace/forge/issues) + +When reporting a bug, include: + +- the tool or page +- input type, with secrets redacted +- expected result +- actual result +- browser and operating system +- screenshots when useful + +## Share Feedback + +Use the in-app feedback page from your Forge workspace, or open a GitHub +discussion through the project repository when that is more convenient. + +## Sensitive Data + +Do not share real tokens, passwords, API keys, customer data, or private source +code in public issues. Redact or replace values with safe examples. diff --git a/docs/tools.md b/docs/tools.md new file mode 100644 index 0000000..c7644d9 --- /dev/null +++ b/docs/tools.md @@ -0,0 +1,84 @@ +# Tool Guide + +Forge groups tools by workflow, not by implementation detail. + +## Editors + +### Markdown Preview + +Write Markdown and preview the rendered result side by side. Useful for docs, +release notes, READMEs, and quick content drafts. + +### HTML Preview + +Write HTML and inspect the result in a constrained preview surface. Useful for +snippets, templates, and quick markup checks. + +### Diff Checker + +Compare original and changed text with split and unified review modes. It +supports whitespace controls, unchanged-line hiding, synchronized scrolling, +syntax selection, copy actions, and export. + +## Data + +### JSON Formatter + +Format, validate, inspect, edit, and copy JSON. It supports spacing options, +line wrapping control, syntax coloring, and text/tree views. + +### JSON YAML Converter + +Convert between JSON and YAML for config and API workflows. + +### JWT Decoder + +Decode JWT headers and payloads, inspect claim details, generate examples, and +verify HMAC signatures locally when supported. + +## Encoding + +### Base64 + +Encode and decode text using standard and URL-safe alphabets. + +### URL Encoder + +Encode, decode, and inspect URL-safe strings for links, query parameters, and +redirect values. + +## Crypto + +### JWT Secret Generator + +Generate HMAC-friendly JWT secrets with common preset lengths. + +### Hash Generator + +Generate common hashes from text input and copy individual outputs. + +### Password Generator + +Generate passwords with configurable length and character sets. + +## Utilities + +### UUID Generator + +Generate UUIDs for identifiers, test data, and local workflows. + +### Timestamp Converter + +Convert Unix timestamps and human-readable dates. + +### Case Converter + +Convert text between common naming styles. + +### Slugify + +Create URL-friendly slugs from human text. + +### Regex Tester + +Test regular expressions against sample input with match feedback. diff --git a/index.html b/index.html index 00533e7..6b53d93 100644 --- a/index.html +++ b/index.html @@ -5,24 +5,31 @@ - + + + + + + + - + + Forge diff --git a/public/_redirects b/public/_redirects new file mode 100644 index 0000000..7797f7c --- /dev/null +++ b/public/_redirects @@ -0,0 +1 @@ +/* /index.html 200 diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..290e673 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://forge.cuthanhcam.workers.dev/sitemap.xml diff --git a/public/site.webmanifest b/public/site.webmanifest new file mode 100644 index 0000000..6f1c447 --- /dev/null +++ b/public/site.webmanifest @@ -0,0 +1,25 @@ +{ + "name": "Forge", + "short_name": "Forge", + "description": "A local-first developer workstation for everyday developer tools.", + "start_url": "/", + "scope": "/", + "display": "standalone", + "background_color": "#09070f", + "theme_color": "#09070f", + "icons": [ + { + "src": "/forge-black.svg", + "sizes": "any", + "type": "image/svg+xml", + "purpose": "any" + }, + { + "src": "/forge-white.svg", + "sizes": "any", + "type": "image/svg+xml", + "purpose": "monochrome" + } + ], + "categories": ["developer", "productivity", "utilities"] +} diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 100644 index 0000000..ee9d3b0 --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1,72 @@ + + + + https://forge.cuthanhcam.workers.dev/ + + + https://forge.cuthanhcam.workers.dev/docs + + + https://forge.cuthanhcam.workers.dev/docs/guides + + + https://forge.cuthanhcam.workers.dev/docs/shortcuts + + + https://forge.cuthanhcam.workers.dev/privacy + + + https://forge.cuthanhcam.workers.dev/terms + + + https://forge.cuthanhcam.workers.dev/support + + + https://forge.cuthanhcam.workers.dev/tools/markdown-preview + + + https://forge.cuthanhcam.workers.dev/tools/html-preview + + + https://forge.cuthanhcam.workers.dev/tools/diff-checker + + + https://forge.cuthanhcam.workers.dev/tools/json-formatter + + + https://forge.cuthanhcam.workers.dev/tools/json-yaml + + + https://forge.cuthanhcam.workers.dev/tools/jwt-decoder + + + https://forge.cuthanhcam.workers.dev/tools/base64 + + + https://forge.cuthanhcam.workers.dev/tools/url-encoder + + + https://forge.cuthanhcam.workers.dev/tools/jwt-secret + + + https://forge.cuthanhcam.workers.dev/tools/hash-generator + + + https://forge.cuthanhcam.workers.dev/tools/password-generator + + + https://forge.cuthanhcam.workers.dev/tools/uuid + + + https://forge.cuthanhcam.workers.dev/tools/timestamp + + + https://forge.cuthanhcam.workers.dev/tools/case-converter + + + https://forge.cuthanhcam.workers.dev/tools/slugify + + + https://forge.cuthanhcam.workers.dev/tools/regex-tester + + diff --git a/src/app/styles.css b/src/app/styles.css index 47e5553..0417873 100644 --- a/src/app/styles.css +++ b/src/app/styles.css @@ -144,6 +144,7 @@ body { color: rgb(15 23 42); font-size: 14px; line-height: 1.7; + overflow-wrap: anywhere; } .forge-markdown > *:first-child { @@ -154,6 +155,10 @@ body { margin-bottom: 0; } +.forge-markdown * { + max-width: 100%; +} + .forge-markdown h1, .forge-markdown h2, .forge-markdown h3, @@ -335,6 +340,18 @@ body { overflow-x: auto; } +@media (max-width: 640px) { + .home-hero-copy { + max-width: calc(100vw - 2rem); + } + + .docs-markdown p, + .docs-markdown li, + .docs-markdown blockquote { + max-width: calc(100vw - 5.5rem); + } +} + .forge-markdown .katex-display { overflow-x: auto; overflow-y: hidden; diff --git a/src/core/registry/tool.definition.ts b/src/core/registry/tool.definition.ts index 8c473c3..73d53bb 100644 --- a/src/core/registry/tool.definition.ts +++ b/src/core/registry/tool.definition.ts @@ -6,6 +6,7 @@ export type ToolStatus = "planned" | "available"; export interface ToolDefinition { category: ToolCategory; description: string; + features: string[]; icon: LucideIcon; id: string; keywords: string[]; diff --git a/src/core/registry/tool.registry.ts b/src/core/registry/tool.registry.ts index 996e3e4..e29a004 100644 --- a/src/core/registry/tool.registry.ts +++ b/src/core/registry/tool.registry.ts @@ -24,6 +24,7 @@ export const toolRegistry: ToolDefinition[] = [ name: "Markdown Preview", category: "Editors", description: "Edit Markdown and preview rendered output side by side.", + features: ["Split preview", "Mermaid", "KaTeX", "Export"], keywords: ["markdown", "preview", "editor", "html"], icon: FileText, persist: true, @@ -35,6 +36,7 @@ export const toolRegistry: ToolDefinition[] = [ name: "HTML Preview", category: "Editors", description: "Write HTML and inspect a sandboxed live preview.", + features: ["Live preview", "Sandbox", "Viewport checks", "Export"], keywords: ["html", "preview", "sandbox", "iframe"], icon: FileCode2, persist: true, @@ -46,6 +48,7 @@ export const toolRegistry: ToolDefinition[] = [ name: "Diff Checker", category: "Editors", description: "Compare two text inputs with focused diff controls.", + features: ["Split diff", "Unified diff", "Inline changes", "Sync scroll"], keywords: ["diff", "compare", "text", "changes"], icon: Diff, persist: true, @@ -57,6 +60,7 @@ export const toolRegistry: ToolDefinition[] = [ name: "JSON Formatter", category: "Data", description: "Format, validate, and inspect JSON payloads.", + features: ["Format", "Minify", "Sort keys", "Tree view"], keywords: ["json", "format", "validate", "beautify"], icon: Braces, persist: true, @@ -68,6 +72,7 @@ export const toolRegistry: ToolDefinition[] = [ name: "JSON YAML Converter", category: "Data", description: "Convert between JSON and YAML for config workflows.", + features: ["JSON to YAML", "YAML to JSON", "Validation", "Editable result"], keywords: ["json", "yaml", "convert", "config"], icon: Code2, persist: true, @@ -79,6 +84,7 @@ export const toolRegistry: ToolDefinition[] = [ name: "JWT Decoder", category: "Data", description: "Decode JWT headers and payloads without sending tokens away.", + features: ["Decode", "Claims", "Verify HS", "Generate examples"], keywords: ["jwt", "token", "decode", "claims"], icon: ShieldCheck, persist: false, @@ -90,6 +96,7 @@ export const toolRegistry: ToolDefinition[] = [ name: "Base64", category: "Encoding", description: "Encode and decode Base64 strings locally.", + features: ["Encode", "Decode", "URL alphabet", "UTF-16LE"], keywords: ["base64", "encode", "decode"], icon: Binary, persist: false, @@ -101,6 +108,7 @@ export const toolRegistry: ToolDefinition[] = [ name: "URL Encoder", category: "Encoding", description: "Encode and decode URL components safely.", + features: ["Component", "Full URL", "Form mode", "Query breakdown"], keywords: ["url", "encode", "decode", "uri"], icon: Link, persist: false, @@ -112,6 +120,7 @@ export const toolRegistry: ToolDefinition[] = [ name: "JWT Secret Generator", category: "Crypto", description: "Generate secure HS256, HS384, and HS512 JWT secrets locally.", + features: ["HS256", "HS384", "HS512", "Masked output"], keywords: ["jwt", "secret", "hs256", "hs512", "hmac"], icon: ShieldCheck, persist: false, @@ -123,6 +132,7 @@ export const toolRegistry: ToolDefinition[] = [ name: "Hash Generator", category: "Crypto", description: "Generate common hashes from local text input.", + features: ["SHA-256", "SHA-512", "MD5", "Copy rows"], keywords: ["hash", "sha", "sha256", "sha512"], icon: Hash, persist: false, @@ -134,6 +144,7 @@ export const toolRegistry: ToolDefinition[] = [ name: "Password Generator", category: "Crypto", description: "Generate strong local passwords with configurable rules.", + features: ["Length presets", "Character sets", "Entropy", "Show/hide"], keywords: ["password", "generator", "random", "security"], icon: KeyRound, persist: false, @@ -145,6 +156,7 @@ export const toolRegistry: ToolDefinition[] = [ name: "UUID Generator", category: "Utilities", description: "Generate single or batch UUID values.", + features: ["UUID v4", "Batch", "Uppercase", "Copy list"], keywords: ["uuid", "guid", "random"], icon: Fingerprint, persist: false, @@ -156,6 +168,7 @@ export const toolRegistry: ToolDefinition[] = [ name: "Timestamp Converter", category: "Utilities", description: "Convert Unix timestamps and readable dates.", + features: ["Unix seconds", "Milliseconds", "ISO", "Time zones"], keywords: ["timestamp", "date", "time", "unix"], icon: Clock3, persist: false, @@ -167,6 +180,7 @@ export const toolRegistry: ToolDefinition[] = [ name: "Case Converter", category: "Utilities", description: "Convert text between common naming conventions.", + features: ["camelCase", "snake_case", "kebab-case", "PascalCase"], keywords: ["case", "camel", "snake", "kebab", "pascal"], icon: TextCursorInput, persist: false, @@ -178,6 +192,7 @@ export const toolRegistry: ToolDefinition[] = [ name: "Slugify", category: "Utilities", description: "Create clean URL slugs from human-readable text.", + features: ["Slug rules", "Separators", "Lowercase", "Preview"], keywords: ["slug", "url", "text"], icon: Pilcrow, persist: false, @@ -189,6 +204,7 @@ export const toolRegistry: ToolDefinition[] = [ name: "Regex Tester", category: "Utilities", description: "Test regular expressions against sample input.", + features: ["Flags", "Matches", "Replace", "Groups"], keywords: ["regex", "regexp", "pattern", "match"], icon: Sparkles, persist: true, diff --git a/src/core/search/search.service.ts b/src/core/search/search.service.ts index fcaa381..a3f598c 100644 --- a/src/core/search/search.service.ts +++ b/src/core/search/search.service.ts @@ -28,6 +28,7 @@ function scoreTool(tool: ToolDefinition, query: string): number { const name = normalize(tool.name); const category = normalize(tool.category); const description = normalize(tool.description); + const features = tool.features.map(normalize); const keywords = tool.keywords.map(normalize); if (name === query) { @@ -46,6 +47,15 @@ function scoreTool(tool: ToolDefinition, query: string): number { return 50; } + if ( + features.some( + (feature) => + feature === query || feature.startsWith(query) || feature.includes(query), + ) + ) { + return 45; + } + if (category.includes(query)) { return 30; } diff --git a/src/features/base64/base64.service.test.ts b/src/features/base64/base64.service.test.ts index d9a392a..8e81393 100644 --- a/src/features/base64/base64.service.test.ts +++ b/src/features/base64/base64.service.test.ts @@ -25,7 +25,7 @@ describe("base64 service", () => { }); it("supports Base64URL without padding", () => { - const encoded = encodeBase64("https://forge.local/?a=1&b=2", { + const encoded = encodeBase64("https://forge.cuthanhcam.workers.dev/?raw=???", { padding: false, variant: "url", }); @@ -33,7 +33,7 @@ describe("base64 service", () => { expect(encoded).not.toContain("="); expect(encoded).not.toContain("+"); expect(decodeBase64(encoded, { variant: "url" }).value).toBe( - "https://forge.local/?a=1&b=2", + "https://forge.cuthanhcam.workers.dev/?raw=???", ); expect(detectBase64Variant(encoded)).toBe("url"); }); diff --git a/src/features/diff-checker/DiffCheckerPage.tsx b/src/features/diff-checker/DiffCheckerPage.tsx index a1948ad..c94625a 100644 --- a/src/features/diff-checker/DiffCheckerPage.tsx +++ b/src/features/diff-checker/DiffCheckerPage.tsx @@ -98,9 +98,9 @@ export function DiffCheckerPage(): JSX.Element { } return ( -
-
-
+
+
+
+ {storySteps.map((step) => ( +
+

+ {step.eyebrow} +

+

+ {step.title} +

+

{step.text}

+
+ ))} +
+
+

- Design language + Forge character

- The interface is intentionally quiet because the input is usually noisy. + A workstation should feel steady enough for secrets and light enough for + tiny tasks.

- Tool screens use dense panes, restrained borders, clear copy actions, and - predictable controls. The home page is the front door; the tools are the - workshop. Both should feel like they belong to the same place. + Forge keeps the interface close to the material: editors, panes, toggles, + copy buttons, validation states, and exports stay where a developer + expects them. The personality comes from restraint, rhythm, and the + feeling that every small tool belongs to the same bench.

@@ -165,7 +241,10 @@ function ComparisonCard({
{items.map((item) => ( -
+
diff --git a/src/pages/legal/LegalPage.tsx b/src/pages/legal/LegalPage.tsx index 66d2651..6fa20fb 100644 --- a/src/pages/legal/LegalPage.tsx +++ b/src/pages/legal/LegalPage.tsx @@ -1,43 +1,142 @@ import type { JSX } from "react"; import { Link, useLocation } from "react-router"; -import { LockKeyhole, ScrollText, ShieldCheck } from "lucide-react"; +import { LockKeyhole, Mail, ScrollText } from "lucide-react"; import { MainLayout } from "@/layouts/MainLayout"; -const privacySections = [ +interface LegalSection { + body: string[]; + title: string; +} + +const privacySections: LegalSection[] = [ + { + title: "Overview", + body: [ + "Forge is designed as a local-first developer workstation for formatting, decoding, comparing, encoding, generating, and validating everyday developer input. This Privacy Policy explains how Forge is intended to handle information when you use the hosted application or a self-hosted copy.", + "Forge does not require an account for its core tools. The product is built to keep common utility workflows in the browser whenever possible, especially for fragments that may be temporary or sensitive, such as JWTs, JSON payloads, source snippets, generated secrets, URLs, hashes, and passwords.", + ], + }, { - body: "Forge is designed as a local-first developer workstation. Tool input such as JWTs, secrets, payloads, source snippets, encoded text, and generated values is processed in your browser whenever the tool supports local execution.", - title: "Local-first processing", + title: "Information processed in the browser", + body: [ + "Most Forge tools process input directly in your browser. Examples include formatting JSON, converting JSON and YAML, decoding JWT header and payload data, generating UUIDs, creating passwords, generating JWT secrets, hashing text, testing regular expressions, encoding or decoding Base64, and encoding or decoding URLs.", + "When a tool runs locally, the text you enter is used by the JavaScript application in your current browser session to produce the requested result. Forge is not designed to send that tool input to an application server for processing.", + ], }, { - body: "Forge may store tool drafts, theme preference, sidebar preference, and recent workspace state in browser storage on this device. This keeps workflows convenient without requiring an account.", title: "Browser storage", + body: [ + "Forge may use browser storage to remember preferences and workspace state. This can include theme preference, sidebar state, recently used tools, command palette behavior, and tool drafts where persistence makes the workflow more useful.", + "Browser storage remains on the device and browser profile where Forge is used until it is cleared by the user, the browser, or site settings. Anyone with access to that device, browser profile, extensions, or developer tools may be able to inspect locally stored data.", + ], }, { - body: "Do not paste production secrets unless you understand the environment you are running Forge in. If you deploy Forge yourself, your hosting, telemetry, proxy, browser extension, or network policies may affect privacy.", - title: "Sensitive content", + title: "Sensitive developer data", + body: [ + "Forge is useful for sensitive developer fragments, but local-first processing is not the same as a guarantee that all use is safe. You should avoid pasting production credentials, customer data, private source code, or regulated information unless you trust the device, browser, deployment, extensions, and network environment you are using.", + "If you accidentally expose a secret in a screenshot, support message, public issue, shared machine, or untrusted environment, rotate that secret through the system that owns it.", + ], }, { - body: "Support links may open email, GitHub, or LinkedIn. Information you choose to send through those services is handled by those providers and by the repository maintainers receiving it.", - title: "External services", + title: "Hosted deployments and self-hosted copies", + body: [ + "The public Forge deployment is hosted as a static web application. If you run Forge from another domain, fork, local build, preview environment, or internal deployment, the privacy behavior may depend on that environment and any analytics, proxies, headers, access logs, extensions, or platform services added around it.", + "Self-hosted operators are responsible for understanding and communicating the privacy behavior of their own deployment.", + ], + }, + { + title: "Support and external services", + body: [ + "Forge may link to external services such as GitHub, email, or LinkedIn for support, feedback, issue reporting, and project information. Information you choose to send through those services is handled by the relevant provider and by the maintainers who receive it.", + "Do not include private tokens, passwords, customer data, proprietary source code, or production secrets in public GitHub issues. Use redacted examples whenever possible.", + ], + }, + { + title: "No account profiles or advertising model", + body: [ + "Forge does not require a user account for core functionality and is not designed around advertising profiles. The product focus is utility work, not behavioral tracking.", + "Future deployments may add operational telemetry or error reporting. If that happens, the privacy documentation should be updated to explain what is collected, why it is collected, and how users can control it.", + ], + }, + { + title: "Your controls", + body: [ + "You can clear Forge data through your browser's site data settings. You can also avoid persistence by using private browsing modes, disabling storage for the site, or using a trusted local deployment for sensitive work.", + "You control what you paste into Forge, what you copy out, and what you send through support channels.", + ], + }, + { + title: "Contact", + body: [ + "For privacy questions, contact the project maintainer at cuthanhcam04@gmail.com or open a non-sensitive issue in the Forge repository.", + "This policy may change as Forge evolves. Material changes should be reflected in the application and project documentation.", + ], }, ]; -const termsSections = [ +const termsSections: LegalSection[] = [ + { + title: "Agreement to these terms", + body: [ + "These Terms of Use describe the expectations for using Forge as a developer utility. By using the hosted application or a copy of Forge, you agree to use it responsibly and to review outputs before relying on them in production or security-sensitive environments.", + "Forge is built to make everyday developer work calmer and faster. It is not a substitute for professional security review, legal review, compliance review, cryptographic review, or production validation.", + ], + }, + { + title: "Description of the service", + body: [ + "Forge provides browser-based tools for previewing Markdown and HTML, comparing text, formatting JSON, converting JSON and YAML, decoding JWTs, encoding and decoding values, generating secrets and passwords, hashing text, generating UUIDs, converting timestamps, converting case, creating slugs, and testing regular expressions.", + "Features may change over time. Some functionality may be added, removed, renamed, or adjusted as the product matures.", + ], + }, + { + title: "Your responsibility for input and output", + body: [ + "You are responsible for the data you enter into Forge and for how you use the results. Generated passwords, JWT secrets, UUIDs, hashes, encodings, timestamps, slugs, regex matches, formatted payloads, decoded claims, and converted files should be checked against your own requirements before use.", + "Do not rely on Forge as the sole control for security, compliance, authentication, authorization, data retention, or incident response decisions.", + ], + }, + { + title: "Sensitive information", + body: [ + "You should avoid entering production secrets, customer data, regulated data, proprietary source code, or other sensitive information unless you understand and trust the environment where Forge is running.", + "If you share support requests, screenshots, examples, or issue reports, redact sensitive values first. Public issue trackers are not appropriate places for private credentials or customer information.", + ], + }, { - body: "Forge is provided as a developer utility. You are responsible for checking outputs before using them in production systems, security-sensitive flows, or customer-facing environments.", - title: "Use at your own discretion", + title: "Generated values and security", + body: [ + "Forge may generate passwords, JWT secrets, UUIDs, hashes, and other values. These tools are intended to support developer workflows, but you remain responsible for selecting appropriate length, entropy, algorithms, storage practices, rotation policies, and access controls.", + "A value that is generated locally can still become unsafe if it is copied into an insecure location, reused incorrectly, committed to source control, sent through chat, stored in browser history, or exposed through screenshots.", + ], }, { - body: "Generated passwords, JWT secrets, UUIDs, hashes, timestamps, encodings, and conversions should be validated against your own security, compliance, and application requirements.", - title: "Generated output", + title: "Availability and changes", + body: [ + "Forge may be unavailable, contain bugs, or produce unexpected results. The hosted application may change without notice, and self-hosted copies may behave differently depending on version, build configuration, hosting platform, and browser environment.", + "You should keep critical workflows backed by appropriate tests, reviews, and production-grade tooling.", + ], }, { - body: "Forge is not a legal, compliance, cryptography, or security audit service. Treat it as a local tool that helps you inspect and transform data faster.", - title: "No professional advice", + title: "Open source and third-party components", + body: [ + "Forge is built with open-source libraries and browser platform APIs. Those components may have their own licenses, limitations, and update cycles.", + "If you redistribute, modify, or self-host Forge, you are responsible for complying with applicable licenses and for reviewing the security and behavior of the version you deploy.", + ], }, { - body: "When you report issues or share feedback, avoid posting private tokens, customer data, credentials, or proprietary source code in public GitHub issues.", - title: "Responsible reporting", + title: "No warranties", + body: [ + "Forge is provided as a practical utility. To the maximum extent permitted by applicable law, it is provided without warranties of any kind, whether express or implied, including warranties of accuracy, fitness for a particular purpose, availability, security, or non-infringement.", + "Use Forge at your own discretion and verify results before applying them to important systems.", + ], + }, + { + title: "Contact", + body: [ + "Questions about these terms can be sent to cuthanhcam04@gmail.com. Non-sensitive product issues can be reported through the Forge GitHub repository.", + "These terms may be updated as Forge changes. Continued use of Forge after updates means you accept the revised terms.", + ], }, ]; @@ -45,21 +144,22 @@ export function LegalPage(): JSX.Element { const location = useLocation(); const isTerms = location.pathname === "/terms"; const sections = isTerms ? termsSections : privacySections; + const title = isTerms ? "Terms of Use" : "Privacy Policy"; return ( -
-
-
-
+
+
+
+
{isTerms ? (
-

- {isTerms ? "Practical terms for practical tools" : "Private by default"} -

-

- {isTerms - ? "Forge is built to make everyday developer work calmer and faster, but it does not replace careful review of security-sensitive output." - : "Forge is most useful with sensitive text because its core tools are intended to run locally, close to your browser session and away from unnecessary accounts."} +

+ Forge legal +

+

+ Last updated: July 5, 2026

-
+
{sections.map((section) => ( -
-

+
+

{section.title} -

-

- {section.body} -

+

+
+ {section.body.map((paragraph) => ( +

{paragraph}

+ ))} +
))}
- + + ); } @@ -267,7 +311,7 @@ function HelpMenuItem({ to, }: HelpMenuItemProps): JSX.Element { const className = - "flex h-8 w-full items-center gap-2 rounded-md px-2 text-left text-[13px] font-semibold text-slate-700 transition hover:bg-sky-50 hover:text-sky-700"; + "flex h-8 w-full items-center gap-2 rounded-md px-2 text-left text-[13px] font-semibold text-slate-700 transition hover:bg-sky-50 hover:text-sky-700 dark:text-pink-50 dark:hover:bg-pink-400/12 dark:hover:text-pink-200 dark:[&.active]:bg-pink-400/14 dark:[&.active]:text-pink-200 dark:[&.active]:ring-1 dark:[&.active]:ring-inset dark:[&.active]:ring-pink-300/20"; if (to) { return ( diff --git a/src/widgets/sidebar/SidebarGroup.tsx b/src/widgets/sidebar/SidebarGroup.tsx index e4c36b4..2978f42 100644 --- a/src/widgets/sidebar/SidebarGroup.tsx +++ b/src/widgets/sidebar/SidebarGroup.tsx @@ -53,8 +53,8 @@ export function SidebarGroup({ open ? "grid-rows-[1fr] opacity-100" : "grid-rows-[0fr] opacity-0", )} > -
-
+
+
{tools.map((tool) => ( ))} diff --git a/src/widgets/sidebar/SidebarToolItem.tsx b/src/widgets/sidebar/SidebarToolItem.tsx index 7aff21a..9e8dcdd 100644 --- a/src/widgets/sidebar/SidebarToolItem.tsx +++ b/src/widgets/sidebar/SidebarToolItem.tsx @@ -24,7 +24,7 @@ export function SidebarToolItem({ collapsed && isActive, "min-h-8 gap-2 rounded-md px-2.5 py-1.5 text-slate-600 hover:bg-slate-100 hover:text-slate-950": !collapsed && !isActive, - "min-h-8 gap-2 rounded-md bg-sky-50 px-2.5 py-1.5 text-sky-700 ring-1 ring-sky-100": + "min-h-8 gap-2 rounded-md bg-sky-50 px-2.5 py-1.5 text-sky-700 ring-1 ring-inset ring-sky-200": !collapsed && isActive, }) } diff --git a/tests/app.test.tsx b/tests/app.test.tsx index 5a4e7d7..9488836 100644 --- a/tests/app.test.tsx +++ b/tests/app.test.tsx @@ -9,7 +9,7 @@ describe("App", () => { expect( screen.getByRole("heading", { - name: /Developer tools without the tab drift\./, + name: /Where rough input becomes usable work\./, }), ).toBeInTheDocument(); expect( @@ -26,6 +26,8 @@ describe("App", () => { expect(screen.getByRole("dialog")).toBeInTheDocument(); expect(screen.getByRole("textbox", { name: "Search tools" })).toHaveFocus(); + expect(screen.getByText("16 tools")).toBeInTheDocument(); + expect(screen.getByText("Tree view")).toBeInTheDocument(); }); it("opens the keyboard shortcuts dialog", async () => {