diff --git a/.github/assets/desktop.webp b/.github/assets/desktop.webp new file mode 100644 index 0000000..7f3eaf3 Binary files /dev/null and b/.github/assets/desktop.webp differ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..29174ce --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,133 @@ +name: Release + +on: + push: + tags: + - "v*" + +permissions: + contents: write + +jobs: + checks: + name: Checks + runs-on: ubuntu-24.04 + + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Install uv + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + + - name: Set up Python + run: uv python install 3.12 + + - name: API dependencies + working-directory: api + run: uv sync --locked + + - name: API lint + working-directory: api + run: uv run ruff check . + + - name: Set up pnpm + uses: pnpm/action-setup@v6 + with: + version: 11.10.0 + + - name: Set up Node + uses: actions/setup-node@v7 + with: + node-version: 24 + + - name: Desktop dependencies + working-directory: desktop + run: pnpm install --frozen-lockfile + + - name: Desktop typecheck + working-directory: desktop + run: pnpm typecheck + + - name: Desktop lint + working-directory: desktop + run: pnpm lint + + - name: Extension dependencies + working-directory: extension + run: pnpm install --frozen-lockfile + + - name: Extension typecheck + working-directory: extension + run: pnpm typecheck + + - name: Extension lint + working-directory: extension + run: pnpm lint + + release: + name: Build And Release + runs-on: ubuntu-24.04 + needs: checks + + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Install Linux packaging dependencies + run: | + sudo apt-get update + sudo apt-get install -y libarchive-tools rpm + + - name: Install uv + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + + - name: Set up Python + run: uv python install 3.12 + + - name: API dependencies + working-directory: api + run: uv sync --locked + + - name: Package API runtime + working-directory: api + run: uv run package-runtime + + - name: Set up pnpm + uses: pnpm/action-setup@v6 + with: + version: 11.10.0 + + - name: Set up Node + uses: actions/setup-node@v7 + with: + node-version: 22 + + - name: Desktop dependencies + working-directory: desktop + run: pnpm install --frozen-lockfile + + - name: Build desktop + working-directory: desktop + run: pnpm build + + - name: Extension dependencies + working-directory: extension + run: pnpm install --frozen-lockfile + + - name: Build extension + working-directory: extension + run: pnpm zip + + - name: Create GitHub release + uses: softprops/action-gh-release@v2 + with: + prerelease: true + generate_release_notes: true + files: | + desktop/release/**/* + extension/.output/*.zip diff --git a/README.md b/README.md index c99ea71..ccd67c6 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,13 @@ Draftlet captures conversations, generates drafts through local LLM providers (Ollama first), keeps durable local memory, and lets you review everything in a focused desktop writing surface without sending your data to a cloud service. +

+ Draftlet desktop app +

+ ## Status -Draftlet is in active alpha development as `v1.0.0-alpha1`. It is not published +Draftlet is in active alpha development as `v1.0.0-alpha1.1`. It is not published as a release yet. Current working scope: @@ -28,14 +32,6 @@ Current working scope: - **Runtime** - local FastAPI service with SQLite persistence, capture ingest, connector management, search, and local generation through Ollama. -Still in progress: - -- Gmail OAuth/API sync. -- Gmail send and compose insertion. -- Packaged runtime installation. -- Production hardening and installer polish. -- Windows/macOS support. - ## Repository Layout ```text @@ -62,13 +58,6 @@ Folder READMEs contain build and run instructions for each surface: - Provider connector: Ollama. - Local persistence in SQLite. -## Out Of Scope For Now - -- Cloud sync, accounts, billing, or team features. -- Auto-capture across arbitrary apps. -- WhatsApp native desktop capture. -- Gmail OAuth/API sync or external email sending. - ## License AGPLv3. See [`LICENSE`](./LICENSE). diff --git a/api/.gitignore b/api/.gitignore index 4bf7cae..bd93d62 100644 --- a/api/.gitignore +++ b/api/.gitignore @@ -11,6 +11,7 @@ __pycache__/ build/ develop-eggs/ dist/ +dist-runtime/ downloads/ eggs/ .eggs/ diff --git a/api/README.md b/api/README.md index 270d200..c804a8e 100644 --- a/api/README.md +++ b/api/README.md @@ -27,6 +27,14 @@ uv run ruff check . uv build ``` +## Package Runtime + +```bash +uv run package-runtime +``` + +The packaged runtime is written to `dist-runtime/` for the Electron build. + ## Telegram Environment Telegram uses MTProto user-client auth. Configure these before connecting diff --git a/api/pyproject.toml b/api/pyproject.toml index b0aeadc..b7d4d70 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "draftlet-api" -version = "1.0.0-alpha1" +version = "1.0.0a1.post1" requires-python = ">=3.12" license = "AGPL-3.0-only" dependencies = [ @@ -16,6 +16,8 @@ dependencies = [ [project.scripts] dev = "draftlet_api:dev" +draftlet-runtime = "draftlet_api.runtime:main" +package-runtime = "draftlet_api.scripts.package_runtime:main" alembic-generate = "draftlet_api.scripts.alembic:generate" alembic-upgrade = "draftlet_api.scripts.alembic:upgrade" alembic-downgrade = "draftlet_api.scripts.alembic:downgrade" @@ -27,5 +29,6 @@ build-backend = "uv_build" [dependency-groups] dev = [ + "pyinstaller>=6.0", "ruff>=0.15.21", ] diff --git a/api/src/draftlet_api/database/models/conversation.py b/api/src/draftlet_api/database/models/conversation.py index e4d2cdb..d65b1c0 100644 --- a/api/src/draftlet_api/database/models/conversation.py +++ b/api/src/draftlet_api/database/models/conversation.py @@ -11,10 +11,6 @@ from draftlet_api.database.models.draft import Draft from draftlet_api.database.models.message import Message -if TYPE_CHECKING: - from draftlet_api.database.models.draft import Draft - - def utcnow() -> datetime: return datetime.now(UTC) diff --git a/api/src/draftlet_api/database/models/fts.py b/api/src/draftlet_api/database/models/fts.py deleted file mode 100644 index 20e1a21..0000000 --- a/api/src/draftlet_api/database/models/fts.py +++ /dev/null @@ -1,21 +0,0 @@ -from sqlalchemy import Column, MetaData, String, Table, Text - -metadata = MetaData() - -conversations_fts = Table( - "conversations_fts", - metadata, - Column("id", String(32), nullable=False), - Column("title", String(500), nullable=False), - Column("contact", String(500), nullable=False), - Column("latest_message", Text, nullable=False), -) - -drafts_fts = Table( - "drafts_fts", - metadata, - Column("id", String(32), nullable=False), - Column("title", String(500), nullable=False), - Column("text", Text, nullable=False), - Column("instruction", Text, nullable=False), -) diff --git a/api/src/draftlet_api/dtos/common.py b/api/src/draftlet_api/dtos/common.py deleted file mode 100644 index 8b934d4..0000000 --- a/api/src/draftlet_api/dtos/common.py +++ /dev/null @@ -1,5 +0,0 @@ -from pydantic import BaseModel - - -class Page(BaseModel): - next_cursor: str | None = None diff --git a/api/src/draftlet_api/main.py b/api/src/draftlet_api/main.py index 81e5250..3e0a5d9 100644 --- a/api/src/draftlet_api/main.py +++ b/api/src/draftlet_api/main.py @@ -24,7 +24,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]: def create_app() -> FastAPI: settings = get_settings() configure_logging(settings.log_level) - app = FastAPI(title="Draftlet Runtime", version="1.0.0-alpha1", lifespan=lifespan) + app = FastAPI(title="Draftlet Runtime", version="1.0.0-alpha1.1", lifespan=lifespan) app.add_exception_handler(DraftletApiError, problem_details_handler) if settings.cors_origins: app.add_middleware( diff --git a/api/src/draftlet_api/repositories/conversation_repository.py b/api/src/draftlet_api/repositories/conversation_repository.py deleted file mode 100644 index c87190e..0000000 --- a/api/src/draftlet_api/repositories/conversation_repository.py +++ /dev/null @@ -1,48 +0,0 @@ -from uuid import UUID - -from sqlalchemy import select -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.orm import selectinload - -from draftlet_api.database.models import Conversation, Message - - -class ConversationRepository: - def __init__(self, db: AsyncSession): - self.db = db - - async def list(self, limit: int = 50) -> list[Conversation]: - result = await self.db.scalars( - select(Conversation) - .options( - selectinload(Conversation.messages), selectinload(Conversation.drafts) - ) - .order_by(Conversation.latest_message_at.desc()) - .limit(limit) - ) - return list(result) - - async def get(self, conversation_id: UUID) -> Conversation | None: - result = await self.db.scalars( - select(Conversation) - .where(Conversation.id == conversation_id) - .options( - selectinload(Conversation.messages), selectinload(Conversation.drafts) - ) - ) - return result.first() - - async def add(self, conversation: Conversation) -> Conversation: - self.db.add(conversation) - await self.db.commit() - return await self.get(conversation.id) or conversation - - async def add_message( - self, conversation: Conversation, message: Message - ) -> Message: - self.db.add(message) - conversation.latest_message = message.body - conversation.latest_message_at = message.timestamp - await self.db.commit() - await self.db.refresh(message) - return message diff --git a/api/src/draftlet_api/repositories/draft_repository.py b/api/src/draftlet_api/repositories/draft_repository.py deleted file mode 100644 index 1e950f9..0000000 --- a/api/src/draftlet_api/repositories/draft_repository.py +++ /dev/null @@ -1,42 +0,0 @@ -from uuid import UUID - -from sqlalchemy import select -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.orm import selectinload - -from draftlet_api.database.models import Draft, DraftVariant - - -class DraftRepository: - def __init__(self, db: AsyncSession): - self.db = db - - async def list(self, conversation_id: UUID | None = None) -> list[Draft]: - query = ( - select(Draft) - .options(selectinload(Draft.variants)) - .order_by(Draft.updated_at.desc()) - ) - if conversation_id: - query = query.where(Draft.conversation_id == conversation_id) - return list(await self.db.scalars(query)) - - async def get(self, draft_id: UUID) -> Draft | None: - return ( - await self.db.scalars( - select(Draft) - .where(Draft.id == draft_id) - .options(selectinload(Draft.variants)) - ) - ).first() - - async def save(self, draft: Draft) -> Draft: - self.db.add(draft) - await self.db.commit() - return await self.get(draft.id) or draft - - async def add_variant(self, draft: Draft, variant: DraftVariant) -> DraftVariant: - self.db.add(variant) - await self.db.commit() - await self.db.refresh(variant) - return variant diff --git a/api/src/draftlet_api/routers/health.py b/api/src/draftlet_api/routers/health.py index 42ef7dd..4e185aa 100644 --- a/api/src/draftlet_api/routers/health.py +++ b/api/src/draftlet_api/routers/health.py @@ -23,7 +23,7 @@ async def health(db: AsyncSession = Depends(get_db)) -> HealthRead: telegram_ok = telegram_state == "ready" return HealthRead( status="ok" if database.ok else "degraded", - version="1.0.0-alpha1", + version="1.0.0-alpha1.1", database=database, ollama=ComponentHealth( ok=ollama_ok, diff --git a/api/src/draftlet_api/routers/v1/connectors.py b/api/src/draftlet_api/routers/v1/connectors.py index e3ed089..a0c6394 100644 --- a/api/src/draftlet_api/routers/v1/connectors.py +++ b/api/src/draftlet_api/routers/v1/connectors.py @@ -1,12 +1,17 @@ from uuid import UUID from fastapi import APIRouter, Depends, Response, status +from pydantic import BaseModel +from sqlalchemy import or_, select from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import selectinload from draftlet_api.connectors.gmail.mapper import GmailCapture, capture_from_gmail from draftlet_api.connectors.registry import connector_registry from draftlet_api.connectors.telegram import auth as telegram_auth +from draftlet_api.core.errors import NotFoundError from draftlet_api.database.engine import get_db +from draftlet_api.database.models import Conversation, Draft from draftlet_api.dtos.capture import CaptureRead from draftlet_api.dtos.connector import ( ConnectorCreate, @@ -26,6 +31,15 @@ router = APIRouter(prefix="/connectors", tags=["connectors"]) +class GmailLatestDraftRead(BaseModel): + draft_id: UUID + conversation_id: UUID + subject: str + text: str + gmail_url: str | None = None + updated_at: str + + async def _activate_telegram(db: AsyncSession, username: str | None) -> None: await ConnectorService(db).upsert( ConnectorCreate( @@ -99,6 +113,45 @@ async def ingest_gmail_capture( return capture +@router.get("/gmail/drafts/latest", response_model=GmailLatestDraftRead) +async def latest_gmail_draft(db: AsyncSession = Depends(get_db)) -> GmailLatestDraftRead: + draft = ( + await db.scalars( + select(Draft) + .join(Conversation) + .where( + Draft.status == "ready", + or_(Conversation.connector == "gmail", Conversation.thread_kind == "email"), + ) + .options(selectinload(Draft.variants), selectinload(Draft.conversation)) + .order_by(Draft.updated_at.desc()) + ) + ).first() + + if not draft: + raise NotFoundError("gmail_draft", "latest") + + selected_variant = next( + (variant for variant in draft.variants if variant.id == draft.selected_variant_id), + None, + ) + text = (selected_variant.body if selected_variant else draft.text).strip() + if not text: + raise NotFoundError("gmail_draft", "latest") + + conversation = draft.conversation + metadata = conversation.meta or {} + gmail_url = metadata.get("url") + return GmailLatestDraftRead( + draft_id=draft.id, + conversation_id=conversation.id, + subject=conversation.title, + text=text, + gmail_url=gmail_url if isinstance(gmail_url, str) else None, + updated_at=draft.updated_at.isoformat(), + ) + + @router.post("/{kind}/pause", response_model=ConnectorDaemonStatusRead) async def pause_connector(kind: str) -> ConnectorDaemonStatusRead: status = await connector_registry.pause(kind) diff --git a/api/src/draftlet_api/runtime.py b/api/src/draftlet_api/runtime.py new file mode 100644 index 0000000..d101409 --- /dev/null +++ b/api/src/draftlet_api/runtime.py @@ -0,0 +1,50 @@ +import os +import sys +from pathlib import Path + +from alembic import command +from alembic.config import Config +from platformdirs import user_data_path +from uvicorn import run + +from draftlet_api.core.config import get_settings + + +def runtime_root() -> Path: + if getattr(sys, "frozen", False): + return Path(sys.executable).resolve().parent + return Path(__file__).resolve().parents[2] + + +def configure_production_environment() -> None: + data_dir = user_data_path("draftlet", appauthor=False) + data_dir.mkdir(parents=True, exist_ok=True) + + os.environ.setdefault("ENVIRONMENT", "production") + os.environ.setdefault("DATABASE_URL", f"sqlite+aiosqlite:///{data_dir / 'draftletapi.db'}") + + +def run_migrations() -> None: + root = runtime_root() + config = Config(root / "alembic.ini") + config.set_main_option("script_location", str(root / "alembic")) + command.upgrade(config, "head") + + +def main() -> None: + configure_production_environment() + run_migrations() + get_settings.cache_clear() + settings = get_settings() + run( + "draftlet_api.main:app", + host=settings.host, + port=settings.port, + log_level=settings.log_level, + reload=False, + workers=1, + ) + + +if __name__ == "__main__": + main() diff --git a/api/src/draftlet_api/scripts/package_runtime.py b/api/src/draftlet_api/scripts/package_runtime.py new file mode 100644 index 0000000..72b2d0e --- /dev/null +++ b/api/src/draftlet_api/scripts/package_runtime.py @@ -0,0 +1,64 @@ +import shutil +import subprocess +import sys +from pathlib import Path + + +API_ROOT = Path(__file__).resolve().parents[3] +DIST_RUNTIME = API_ROOT / "dist-runtime" +BUILD_DIR = API_ROOT / "build" / "pyinstaller" +DIST_DIR = API_ROOT / "dist" / "pyinstaller" +RUNTIME_NAME = "draftlet-runtime" + + +def run(command: list[str]) -> None: + print(f"$ {' '.join(command)}", flush=True) + subprocess.run(command, cwd=API_ROOT, check=True) + + +def main() -> None: + shutil.rmtree(DIST_RUNTIME, ignore_errors=True) + shutil.rmtree(BUILD_DIR, ignore_errors=True) + shutil.rmtree(DIST_DIR, ignore_errors=True) + DIST_RUNTIME.mkdir(parents=True) + + run( + [ + sys.executable, + "-m", + "PyInstaller", + "--clean", + "--noconfirm", + "--onefile", + "--name", + RUNTIME_NAME, + "--paths", + str(API_ROOT / "src"), + "--collect-submodules", + "draftlet_api", + "--hidden-import", + "aiosqlite", + "--hidden-import", + "greenlet", + "--distpath", + str(DIST_RUNTIME), + "--workpath", + str(BUILD_DIR), + str(API_ROOT / "src" / "draftlet_api" / "runtime.py"), + ] + ) + + shutil.copy2(API_ROOT / "alembic.ini", DIST_RUNTIME / "alembic.ini") + shutil.copytree( + API_ROOT / "src" / "alembic", + DIST_RUNTIME / "alembic", + ignore=shutil.ignore_patterns("__pycache__", "*.pyc"), + ) + + spec_file = API_ROOT / f"{RUNTIME_NAME}.spec" + if spec_file.exists(): + spec_file.unlink() + + +if __name__ == "__main__": + main() diff --git a/api/src/draftlet_api/services/conversation_service.py b/api/src/draftlet_api/services/conversation_service.py deleted file mode 100644 index 383c20a..0000000 --- a/api/src/draftlet_api/services/conversation_service.py +++ /dev/null @@ -1,96 +0,0 @@ -from datetime import UTC, datetime -from uuid import UUID - -from sqlalchemy.ext.asyncio import AsyncSession - -from draftlet_api.core.errors import NotFoundError -from draftlet_api.database.models import Conversation, Message -from draftlet_api.dtos.conversation import ( - ConversationCreate, - ConversationRead, - ConversationUpdate, -) -from draftlet_api.dtos.message import MessageCreate, MessageRead -from draftlet_api.repositories.conversation_repository import ConversationRepository - - -def message_read(item: Message) -> MessageRead: - return MessageRead( - id=item.id, - kind=item.kind, - author=item.author, - timestamp=item.timestamp, - body=item.body, - status=item.status, - source_message_id=item.source_message_id, - external_message_id=item.external_message_id, - reply_to_message_id=item.reply_to_message_id, - reply_to_external_message_id=item.reply_to_external_message_id, - metadata=item.meta, - ) - - -def conversation_read(item: Conversation) -> ConversationRead: - drafts = item.drafts or [] - - return ConversationRead( - id=item.id, - connector=item.connector, - title=item.title, - contact=item.contact, - participants=item.participants, - source=item.source, - external_thread_id=item.external_thread_id, - thread_kind=item.thread_kind, - metadata=item.meta, - latest_message=item.latest_message, - timestamp=item.latest_message_at, - captured_at=item.captured_at, - draft_pending=any(draft.status in {"generating", "ready"} for draft in drafts), - needs_follow_up=item.needs_follow_up, - recently_captured=item.recently_captured, - draft_ids=[draft.id for draft in drafts], - latest_draft_id=drafts[0].id if drafts else None, - messages=[message_read(message) for message in item.messages], - ) - - -class ConversationService: - def __init__(self, db: AsyncSession): - self.repo = ConversationRepository(db) - - async def list(self, limit: int) -> list[ConversationRead]: - return [conversation_read(item) for item in await self.repo.list(limit)] - - async def get(self, conversation_id: UUID) -> ConversationRead: - item = await self.repo.get(conversation_id) - if not item: - raise NotFoundError("conversation", str(conversation_id)) - return conversation_read(item) - - async def create(self, payload: ConversationCreate) -> ConversationRead: - item = Conversation(**payload.model_dump()) - return conversation_read(await self.repo.add(item)) - - async def update( - self, conversation_id: UUID, payload: ConversationUpdate - ) -> ConversationRead: - item = await self.repo.get(conversation_id) - if not item: - raise NotFoundError("conversation", str(conversation_id)) - for key, value in payload.model_dump(exclude_unset=True).items(): - setattr(item, key, value) - return conversation_read(await self.repo.add(item)) - - async def add_message( - self, conversation_id: UUID, payload: MessageCreate - ) -> MessageRead: - conversation = await self.repo.get(conversation_id) - if not conversation: - raise NotFoundError("conversation", str(conversation_id)) - values = payload.model_dump(exclude_none=True) - values.setdefault("timestamp", datetime.now(UTC)) - message = await self.repo.add_message( - conversation, Message(conversation_id=conversation.id, **values) - ) - return message_read(message) diff --git a/api/src/draftlet_api/services/draft_service.py b/api/src/draftlet_api/services/draft_service.py deleted file mode 100644 index 91fa62a..0000000 --- a/api/src/draftlet_api/services/draft_service.py +++ /dev/null @@ -1,106 +0,0 @@ -from datetime import UTC, datetime -from uuid import UUID - -from sqlalchemy.ext.asyncio import AsyncSession - -from draftlet_api.core.errors import NotFoundError -from draftlet_api.database.models import Draft, DraftVariant, Message -from draftlet_api.dtos.draft import ( - DraftCreate, - DraftRead, - DraftUpdate, - DraftVariantCreate, - DraftVariantRead, - SelectedMessageList, -) -from draftlet_api.repositories.conversation_repository import ConversationRepository -from draftlet_api.repositories.draft_repository import DraftRepository - - -def draft_read(item: Draft) -> DraftRead: - selected_messages = SelectedMessageList.model_validate( - {"items": item.selected_messages} - ) - - return DraftRead( - id=item.id, - conversation_id=item.conversation_id, - status=item.status, - title=item.title, - provider=item.provider, - instruction=item.instruction, - text=item.text, - selected_variant_id=item.selected_variant_id, - reply_target_message_id=item.reply_target_message_id, - send_mode=item.send_mode, - selected_messages=selected_messages.items, - references=item.references, - variants=[ - DraftVariantRead.model_validate(variant) for variant in item.variants - ], - created_at=item.created_at, - updated_at=item.updated_at, - ) - - -class DraftService: - def __init__(self, db: AsyncSession): - self.repo = DraftRepository(db) - self.conversations = ConversationRepository(db) - self.db = db - - async def list(self, conversation_id: UUID | None) -> list[DraftRead]: - return [draft_read(item) for item in await self.repo.list(conversation_id)] - - async def get(self, draft_id: UUID) -> DraftRead: - item = await self.repo.get(draft_id) - if not item: - raise NotFoundError("draft", str(draft_id)) - return draft_read(item) - - async def create(self, payload: DraftCreate) -> DraftRead: - if not await self.conversations.get(payload.conversation_id): - raise NotFoundError("conversation", str(payload.conversation_id)) - item = Draft(**payload.model_dump()) - return draft_read(await self.repo.save(item)) - - async def update(self, draft_id: UUID, payload: DraftUpdate) -> DraftRead: - item = await self.repo.get(draft_id) - if not item: - raise NotFoundError("draft", str(draft_id)) - for key, value in payload.model_dump(exclude_unset=True).items(): - setattr(item, key, value) - return draft_read(await self.repo.save(item)) - - async def add_variant( - self, draft_id: UUID, payload: DraftVariantCreate - ) -> DraftVariantRead: - draft = await self.repo.get(draft_id) - if not draft: - raise NotFoundError("draft", str(draft_id)) - return DraftVariantRead.model_validate( - await self.repo.add_variant( - draft, DraftVariant(draft_id=draft.id, **payload.model_dump()) - ) - ) - - async def accept(self, draft_id: UUID) -> DraftRead: - draft = await self.repo.get(draft_id) - if not draft: - raise NotFoundError("draft", str(draft_id)) - draft.status = "accepted" - conversation = await self.conversations.get(draft.conversation_id) - if conversation: - message = Message( - conversation_id=conversation.id, - kind="accepted", - author="Draftlet", - body=draft.text, - status="Accepted and inserted", - timestamp=datetime.now(UTC), - ) - await self.conversations.add_message(conversation, message) - return draft_read(await self.repo.save(draft)) - - async def mark_sent(self, draft_id: UUID) -> DraftRead: - return await self.update(draft_id, DraftUpdate(status="sent")) diff --git a/api/uv.lock b/api/uv.lock index 1cda33b..6cf9385 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -30,6 +30,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/96/78/5fe6dc3a3a5b2f5a2a4faef8bfe336d5fa049a38884ab3172e0098160c01/alembic-1.18.5-py3-none-any.whl", hash = "sha256:06d8ba9d04558022f5395e9317de03d270f3dced49cee01f89fe7a13c26f14bc", size = 264664, upload-time = "2026-06-25T15:20:56.673Z" }, ] +[[package]] +name = "altgraph" +version = "0.17.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/f8/97fdf103f38fed6792a1601dbc16cc8aac56e7459a9fff08c812d8ae177a/altgraph-0.17.5.tar.gz", hash = "sha256:c87b395dd12fabde9c99573a9749d67da8d29ef9de0125c7f536699b4a9bc9e7", size = 48428, upload-time = "2025-11-21T20:35:50.583Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/ba/000a1996d4308bc65120167c21241a3b205464a2e0b58deda26ae8ac21d1/altgraph-0.17.5-py2.py3-none-any.whl", hash = "sha256:f3a22400bce1b0c701683820ac4f3b159cd301acab067c51c653e06961600597", size = 21228, upload-time = "2025-11-21T20:35:49.444Z" }, +] + [[package]] name = "annotated-doc" version = "0.0.4" @@ -111,7 +120,7 @@ wheels = [ [[package]] name = "draftlet-api" -version = "1.0.0a1" +version = "1.0.0a1.post1" source = { editable = "." } dependencies = [ { name = "aiosqlite" }, @@ -126,6 +135,7 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "pyinstaller" }, { name = "ruff" }, ] @@ -142,7 +152,10 @@ requires-dist = [ ] [package.metadata.requires-dev] -dev = [{ name = "ruff", specifier = ">=0.15.21" }] +dev = [ + { name = "pyinstaller", specifier = ">=6.0" }, + { name = "ruff", specifier = ">=0.15.21" }, +] [[package]] name = "email-validator" @@ -469,6 +482,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] +[[package]] +name = "macholib" +version = "1.16.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "altgraph" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/2f/97589876ea967487978071c9042518d28b958d87b17dceb7cdc1d881f963/macholib-1.16.4.tar.gz", hash = "sha256:f408c93ab2e995cd2c46e34fe328b130404be143469e41bc366c807448979362", size = 59427, upload-time = "2025-11-22T08:28:38.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/d1/a9f36f8ecdf0fb7c9b1e78c8d7af12b8c8754e74851ac7b94a8305540fc7/macholib-1.16.4-py2.py3-none-any.whl", hash = "sha256:da1a3fa8266e30f0ce7e97c6a54eefaae8edd1e5f86f3eb8b95457cae90265ea", size = 38117, upload-time = "2025-11-22T08:28:36.939Z" }, +] + [[package]] name = "mako" version = "1.3.12" @@ -565,6 +590,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pefile" +version = "2024.8.26" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/4f/2750f7f6f025a1507cd3b7218691671eecfd0bbebebe8b39aa0fe1d360b8/pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632", size = 76008, upload-time = "2024-08-26T20:58:38.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/16/12b82f791c7f50ddec566873d5bdd245baa1491bac11d15ffb98aecc8f8b/pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f", size = 74766, upload-time = "2024-08-26T21:01:02.632Z" }, +] + [[package]] name = "platformdirs" version = "4.10.0" @@ -720,6 +763,46 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] +[[package]] +name = "pyinstaller" +version = "6.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "altgraph" }, + { name = "macholib", marker = "sys_platform == 'darwin'" }, + { name = "packaging" }, + { name = "pefile", marker = "sys_platform == 'win32'" }, + { name = "pyinstaller-hooks-contrib" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/4d/ec706c3fcf39e26888c35b39615ff4d5865d184069666c47492cff1fbe50/pyinstaller-6.21.0.tar.gz", hash = "sha256:bb9fab705983e393a2d1cac77d6972513057ad800215fd861dc15ff5272e98fd", size = 4061519, upload-time = "2026-06-13T14:15:06.25Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/4a/53cf98bf66daed012dc9cd78c8203f19a675d696f2fc12afcf8c5049a0e0/pyinstaller-6.21.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:327d132389f37912609e01be62810cf96b5aa95b613903e4b8692e0d12fb0eda", size = 1052350, upload-time = "2026-06-13T14:13:55.88Z" }, + { url = "https://files.pythonhosted.org/packages/30/83/b591295c352ef464c50b4c6ffff1c4f771d875c9e833f578d1b9f564f6b3/pyinstaller-6.21.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7071d4b094d5b40deeef5fa3d3b98a1b846087f7562b49209663d5f9281fe251", size = 748477, upload-time = "2026-06-13T14:14:00.327Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8f/88fff4e403873b1e22286911350e75ff00db014aa08e57045da9d4328993/pyinstaller-6.21.0-py3-none-manylinux2014_i686.whl", hash = "sha256:6b6374d652107dd4a2eeece903ff82bb4045bb5e1006c5a158a6dcdbefe84bf2", size = 760877, upload-time = "2026-06-13T14:14:04.836Z" }, + { url = "https://files.pythonhosted.org/packages/8a/13/f0e48fbdfd1d05d948157121cea8b1b823dcb89efe6934b71fdd8bdb3f0f/pyinstaller-6.21.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:4e3108b3f02384560da70e39b8bf22b0ad597d02bd68a40d76ea91c1cfa00cad", size = 759194, upload-time = "2026-06-13T14:14:10.61Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d5/ea7878cf9924ed30d946d8288777424e6d069d94f5bde56b4d0890069664/pyinstaller-6.21.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:697532279f535ad572bda613db4f821540e235c7854ca6da4d3bf0373f4415ee", size = 754979, upload-time = "2026-06-13T14:14:15.226Z" }, + { url = "https://files.pythonhosted.org/packages/9f/09/51b8905714b733bac66dbc041a7821372d70d888d273ae474c4037d4202d/pyinstaller-6.21.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:605169523a6b5ace39f13dfbff21add9f2bc43df99c7daf9394fefb2c45e8b6f", size = 754812, upload-time = "2026-06-13T14:14:20.264Z" }, + { url = "https://files.pythonhosted.org/packages/4b/43/d77779439d8c6c2e27a77bcfbd1d5cc0f568ebb611bb472b11af81b5f177/pyinstaller-6.21.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:5fa56746c1e76f93634d018502301378a2d0c382553d37d8c3c34ff436c12dd1", size = 753887, upload-time = "2026-06-13T14:14:25.268Z" }, + { url = "https://files.pythonhosted.org/packages/51/8f/c22df1f6837784ac349057ba693f08e7b1ca7a0e06f9c33c63bc6280007b/pyinstaller-6.21.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:42395ec76df8e8120c36b13339d9db8cab83e316a12839ee303cc00fc941bb74", size = 753779, upload-time = "2026-06-13T14:14:29.445Z" }, + { url = "https://files.pythonhosted.org/packages/c9/76/1ce8a27ce62ba8cf3a87c9ce6d575610f4e55d7cb0123e7512fc3f4b921a/pyinstaller-6.21.0-py3-none-win32.whl", hash = "sha256:c6b28d30d8fd99ce162ff3aab5013ed44dbfb747566b1f01b9bed7964d7c14e9", size = 1336462, upload-time = "2026-06-13T14:14:35.785Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fa/ca1d7e5257dd8566a9dfc0dfb02f8a8075eeb53d4b2d3c579f1276759042/pyinstaller-6.21.0-py3-none-win_amd64.whl", hash = "sha256:7fae06c494ce0ebfe6bd3055c0e409def884f63af2e3705d06bd431ad9237fc7", size = 1397487, upload-time = "2026-06-13T14:14:42.328Z" }, + { url = "https://files.pythonhosted.org/packages/dc/75/21b51523ce8d96629b71311775a0a65f5f5a872124ab0de33e5c848f8bff/pyinstaller-6.21.0-py3-none-win_arm64.whl", hash = "sha256:f13c95c9c03fb567217135919f93815c305813126780b0ed6e0123cb8acaf025", size = 1346094, upload-time = "2026-06-13T14:14:48.914Z" }, +] + +[[package]] +name = "pyinstaller-hooks-contrib" +version = "2026.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/5b/c9fe0db5e83ee1c39b2258fa21d23b15e1a60786b6c5990ee5074ead8bb6/pyinstaller_hooks_contrib-2026.6.tar.gz", hash = "sha256:bef5002c32f4f50bd55b005da12cff64eca8783e7eaf86a06a62410164bab725", size = 173354, upload-time = "2026-06-08T22:37:16.152Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/31/f2d7343d8ed5f7c4678377886f6ce533e6eaaa131b252ce950114c2a7efa/pyinstaller_hooks_contrib-2026.6-py3-none-any.whl", hash = "sha256:fd13b8ac126b35361175edacd41a0d97080b75dd5f4b594ecefefff969509dd3", size = 457159, upload-time = "2026-06-08T22:37:14.722Z" }, +] + [[package]] name = "python-dotenv" version = "1.2.2" @@ -738,6 +821,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, ] +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, +] + [[package]] name = "pyyaml" version = "6.0.3" @@ -929,6 +1021,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/36/a8/3fb9a4319efa3b26f5be0e90e6d8918df43fa7c7e977d26390f589501d82/sentry_sdk-2.64.0-py3-none-any.whl", hash = "sha256:715ea91ca860a819e8d8a50a7bde3a80d0df3b4ed7b6660a20fb9a2d084188f1", size = 498901, upload-time = "2026-06-30T08:13:45.566Z" }, ] +[[package]] +name = "setuptools" +version = "83.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/26/f5d29e25ffdb535afef2d35cdb55b325298f96debd670da4c325e08d70f4/setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef", size = 1154254, upload-time = "2026-07-04T15:31:22.699Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/40/e1e72872c6354b306daef1703549e8e83b4d43cfea356311bf722a043752/setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3", size = 1008090, upload-time = "2026-07-04T15:31:20.885Z" }, +] + [[package]] name = "shellingham" version = "1.5.4" diff --git a/desktop/.eslintrc.cjs b/desktop/.eslintrc.cjs deleted file mode 100644 index 84c43a0..0000000 --- a/desktop/.eslintrc.cjs +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = { - root: true, - env: { browser: true, es2020: true }, - extends: [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "plugin:react-hooks/recommended", - ], - ignorePatterns: ["dist", ".eslintrc.cjs"], - parser: "@typescript-eslint/parser", - plugins: ["react-refresh"], - rules: { - "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], - }, -}; diff --git a/desktop/.gitignore b/desktop/.gitignore index 7337953..3b0b41b 100644 --- a/desktop/.gitignore +++ b/desktop/.gitignore @@ -16,6 +16,7 @@ dist-ssr # Editor directories and files .vscode/* +!.vscode/settings.json !.vscode/extensions.json .idea .DS_Store @@ -24,9 +25,6 @@ dist-ssr *.njsproj *.sln *.sw? -!.vscode/ -!.vscode/settings.json -!.vscode/extensions.json dist-electron release diff --git a/desktop/.prettierignore b/desktop/.prettierignore new file mode 100644 index 0000000..1716080 --- /dev/null +++ b/desktop/.prettierignore @@ -0,0 +1,7 @@ +node_modules +dist +dist-electron +release +pnpm-lock.yaml +openapi.json +src/lib/contracts.generated.ts diff --git a/desktop/.prettierrc.json b/desktop/.prettierrc.json new file mode 100644 index 0000000..0c1e103 --- /dev/null +++ b/desktop/.prettierrc.json @@ -0,0 +1,9 @@ +{ + "semi": true, + "singleQuote": false, + "trailingComma": "all", + "printWidth": 100, + "tabWidth": 2, + "useTabs": false, + "plugins": ["prettier-plugin-tailwindcss"] +} diff --git a/desktop/build/icons/512x512.png b/desktop/build/icons/512x512.png new file mode 100644 index 0000000..a0b7da7 Binary files /dev/null and b/desktop/build/icons/512x512.png differ diff --git a/desktop/components.json b/desktop/components.json index 35736c6..3a1804c 100644 --- a/desktop/components.json +++ b/desktop/components.json @@ -5,7 +5,7 @@ "tsx": true, "tailwind": { "config": "", - "css": "src/styles/main.css", + "css": "src/shared/styles/main.css", "baseColor": "neutral", "cssVariables": true, "prefix": "" diff --git a/desktop/electron-builder.json5 b/desktop/electron-builder.json5 index 4a2350f..d693e50 100644 --- a/desktop/electron-builder.json5 +++ b/desktop/electron-builder.json5 @@ -1,16 +1,32 @@ // @see - https://www.electron.build/configuration/configuration { $schema: "https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json", - appId: "dev.draftlet.desktop", + appId: "sreekarnv.draftlet.desktop", asar: true, productName: "Draftlet", + executableName: "draftlet", directories: { output: "release/${version}", }, files: ["dist", "dist-electron"], + extraResources: [ + { + from: "build/icons/512x512.png", + to: "app-icon.png", + }, + { + from: "../api/dist-runtime", + to: "runtime", + }, + ], linux: { - target: ["AppImage"], + target: ["AppImage", "deb"], category: "Utility", - artifactName: "${productName}-Linux-${version}.${ext}", + maintainer: "sreekarnv ", + desktop: { + Name: "Draftlet", + StartupWMClass: "draftlet", + }, + artifactName: "${productName}-Linux-${version}-${arch}.${ext}", }, } diff --git a/desktop/electron/main.ts b/desktop/electron/main.ts index 050e6f7..f464c9c 100644 --- a/desktop/electron/main.ts +++ b/desktop/electron/main.ts @@ -1,8 +1,9 @@ import { app, BrowserWindow, Menu, Tray, ipcMain, nativeImage, net } from "electron"; import { createRequire } from "node:module"; import { spawn, type ChildProcess } from "node:child_process"; -import { fileURLToPath } from "node:url"; +import { fileURLToPath, pathToFileURL } from "node:url"; import path from "node:path"; +import fs from "node:fs"; const require = createRequire(import.meta.url); const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -19,14 +20,26 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); process.env.APP_ROOT = path.join(__dirname, ".."); // 🚧 Use ['ENV_NAME'] avoid vite:define plugin - Vite@2.x -export const VITE_DEV_SERVER_URL = process.env["VITE_DEV_SERVER_URL"]; -export const MAIN_DIST = path.join(process.env.APP_ROOT, "dist-electron"); -export const RENDERER_DIST = path.join(process.env.APP_ROOT, "dist"); +const VITE_DEV_SERVER_URL = process.env["VITE_DEV_SERVER_URL"]; +const RENDERER_DIST = path.join(process.env.APP_ROOT, "dist"); process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL ? path.join(process.env.APP_ROOT, "public") : RENDERER_DIST; +const APP_NAME = "Draftlet"; +const LINUX_APP_CLASS = "draftlet"; + +if (process.platform === "linux") { + app.commandLine.appendSwitch("class", LINUX_APP_CLASS); +} + +app.setName(APP_NAME); +app.setAppUserModelId("sreekarnv.draftlet.desktop"); + +const appWithDesktopName = app as typeof app & { setDesktopName?: (desktopName: string) => void }; +appWithDesktopName.setDesktopName?.(`${LINUX_APP_CLASS}.desktop`); + let win: BrowserWindow | null; let runtime: ChildProcess | null = null; let tray: Tray | null = null; @@ -35,8 +48,9 @@ let runtimeEventsAbort: AbortController | null = null; let runtimeEventsReconnect: ReturnType | null = null; const RUN_IN_BACKGROUND_KEY = "run_in_background"; -const RUNTIME_BASE_URL = "http://127.0.0.1:8000"; -const RUNTIME_EVENTS_URL = "http://127.0.0.1:8000/api/v1/events/stream"; +const RUNTIME_PORT = VITE_DEV_SERVER_URL ? 8000 : 8765; +const RUNTIME_BASE_URL = `http://127.0.0.1:${RUNTIME_PORT}`; +const RUNTIME_EVENTS_URL = `${RUNTIME_BASE_URL}/api/v1/events/stream`; const RUNTIME_AUTH_TOKEN = process.env["DRAFTLET_RUNTIME_TOKEN"]; const RUN_IN_BACKGROUND_PATH = `/api/v1/settings/${RUN_IN_BACKGROUND_KEY}`; const ALLOWED_RUNTIME_PATHS = [ @@ -63,12 +77,24 @@ type RuntimeRequestInit = { type RuntimeEvent = Record & { type?: string }; function startRuntime() { - if (!VITE_DEV_SERVER_URL || runtime) return; - const runtimeRoot = path.resolve(process.env.APP_ROOT, "..", "api"); - runtime = spawn("sh", ["-c", "uv run alembic-upgrade && uv run dev"], { - cwd: runtimeRoot, - stdio: "inherit", - }); + if (runtime) return; + + if (VITE_DEV_SERVER_URL) { + const runtimeRoot = path.resolve(process.env.APP_ROOT, "..", "api"); + runtime = spawn("sh", ["-c", "uv run alembic-upgrade && uv run dev"], { + cwd: runtimeRoot, + stdio: "inherit", + }); + } else { + const executable = process.platform === "win32" ? "draftlet-runtime.exe" : "draftlet-runtime"; + const runtimeRoot = path.join(process.resourcesPath, "runtime"); + runtime = spawn(path.join(runtimeRoot, executable), [], { + cwd: runtimeRoot, + env: { ...process.env, PORT: String(RUNTIME_PORT) }, + stdio: "inherit", + }); + } + runtime.on("exit", () => { runtime = null; }); @@ -138,7 +164,7 @@ async function connectRuntimeEvents() { } } catch (error) { if (!abort.signal.aborted) { - console.info("Runtime event stream disconnected", error); + console.info("Runtime event stream unavailable; retrying", error); } } finally { if (runtimeEventsAbort === abort) { @@ -152,7 +178,7 @@ function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } -async function runtimeRequest(path: string, init?: RuntimeRequestInit, retries = 0) { +async function runtimeRequest(path: string, init?: RuntimeRequestInit, retries = 20) { startRuntime(); const requestInit = withRuntimeAuth(init); @@ -255,11 +281,26 @@ function stopRuntime() { } function getTrayIcon() { - const iconPath = path.join(process.env.VITE_PUBLIC, "electron-vite.svg"); - const icon = nativeImage.createFromPath(iconPath); + const icon = nativeImage.createFromPath(getAppIconPath()); return icon.isEmpty() ? nativeImage.createEmpty() : icon; } +function getAppIconPath() { + for (const iconPath of getAppIconPaths()) { + if (fs.existsSync(iconPath)) return iconPath; + } + + return ""; +} + +function getAppIconPaths() { + return [ + path.join(process.resourcesPath, "app-icon.png"), + path.join(process.env.APP_ROOT, "build", "icons", "512x512.png"), + path.join(process.env.VITE_PUBLIC, "logo.png"), + ]; +} + function restoreWindow(route?: string) { if (!win || win.isDestroyed()) { createWindow(route); @@ -285,13 +326,31 @@ function navigateWindow(route: string) { return; } - void win.webContents.executeJavaScript( - `window.history.pushState(null, "", ${JSON.stringify(route)}); window.dispatchEvent(new PopStateEvent("popstate"));`, - ); + void win.webContents.executeJavaScript(`window.location.hash = ${JSON.stringify(route)};`); } -async function buildTrayMenu() { - const runInBackground = await getRunInBackground(); +function enableDevtoolsShortcuts(window: BrowserWindow) { + if (!VITE_DEV_SERVER_URL) return; + + window.webContents.on("before-input-event", (event, input) => { + const key = input.key.toLowerCase(); + const isDevtoolsShortcut = + key === "f12" || + (key === "i" && input.control && input.shift) || + (key === "i" && input.meta && input.alt); + if (!isDevtoolsShortcut) return; + + event.preventDefault(); + window.webContents.toggleDevTools(); + }); +} + +function quitDraftlet() { + isQuitting = true; + app.quit(); +} + +function setTrayMenu(runInBackground: boolean) { tray?.setToolTip( runInBackground ? "Draftlet - background capture on" : "Draftlet - background capture off", ); @@ -320,19 +379,21 @@ async function buildTrayMenu() { { type: "separator" }, { label: "Quit Draftlet", - click: () => { - isQuitting = true; - app.quit(); - }, + click: quitDraftlet, }, ]), ); } +async function buildTrayMenu() { + setTrayMenu(await getRunInBackground()); +} + function createTray() { if (tray) return; tray = new Tray(getTrayIcon()); + setTrayMenu(false); tray.on("click", () => restoreWindow()); void buildTrayMenu(); } @@ -357,12 +418,14 @@ function createWindow(initialRoute?: string) { x: 0, y: 0, autoHideMenuBar: true, - icon: path.join(process.env.VITE_PUBLIC, "electron-vite.svg"), + icon: getAppIconPath(), webPreferences: { preload: path.join(__dirname, "preload.mjs"), }, }); + enableDevtoolsShortcuts(win); + win.on("close", (event) => { if (isQuitting) return; @@ -383,16 +446,12 @@ function createWindow(initialRoute?: string) { win = null; }); - // Test active push message to Renderer-process. - win.webContents.on("did-finish-load", () => { - win?.webContents.send("main-process-message", new Date().toLocaleString()); - }); - if (VITE_DEV_SERVER_URL) { void win.loadURL(`${VITE_DEV_SERVER_URL}${initialRoute ?? ""}`); } else { - // win.loadFile('dist/index.html') - void win.loadFile(path.join(RENDERER_DIST, "index.html")).then(() => { + const rendererUrl = new URL(pathToFileURL(path.join(RENDERER_DIST, "index.html"))); + rendererUrl.hash = initialRoute ?? "/"; + void win.loadURL(rendererUrl.href).then(() => { if (initialRoute) { navigateWindow(initialRoute); } @@ -430,6 +489,7 @@ if (!gotLock) { void app.whenReady().then(() => { Menu.setApplicationMenu(null); + app.dock?.setIcon(nativeImage.createFromPath(getAppIconPath())); startRuntime(); void connectRuntimeEvents(); createTray(); diff --git a/desktop/eslint.config.mjs b/desktop/eslint.config.mjs new file mode 100644 index 0000000..652bea9 --- /dev/null +++ b/desktop/eslint.config.mjs @@ -0,0 +1,78 @@ +import js from "@eslint/js"; +import eslintConfigPrettier from "eslint-config-prettier/flat"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import globals from "globals"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + { + ignores: [ + "node_modules/**", + "dist/**", + "dist-electron/**", + "release/**", + "src/lib/contracts.generated.ts", + "eslint.config.mjs", + ], + }, + js.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + { + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + rules: { + "@typescript-eslint/consistent-type-imports": [ + "error", + { prefer: "type-imports", fixStyle: "inline-type-imports" }, + ], + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-misused-promises": [ + "error", + { + checksVoidReturn: { + attributes: false, + properties: false, + }, + }, + ], + "@typescript-eslint/no-unsafe-argument": "warn", + "@typescript-eslint/no-unsafe-assignment": "warn", + "@typescript-eslint/no-unsafe-call": "warn", + "@typescript-eslint/no-unsafe-member-access": "warn", + "@typescript-eslint/no-unsafe-return": "warn", + "@typescript-eslint/unbound-method": "warn", + }, + }, + { + files: ["src/**/*.{ts,tsx}"], + languageOptions: { + globals: { + ...globals.browser, + }, + }, + plugins: { + "react-hooks": reactHooks, + "react-refresh": reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + "react-hooks/set-state-in-effect": "warn", + "react-hooks/static-components": "warn", + "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], + }, + }, + { + files: ["electron/**/*.{ts,tsx}", "vite.config.ts"], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + eslintConfigPrettier, +); diff --git a/desktop/index.html b/desktop/index.html index ba8aea9..ca09a59 100644 --- a/desktop/index.html +++ b/desktop/index.html @@ -2,7 +2,7 @@ - + Draftlet diff --git a/desktop/package.json b/desktop/package.json index b318dc8..a200f1a 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -1,18 +1,26 @@ { "name": "@draftlet/desktop", - "version": "1.0.0-alpha1", + "productName": "Draftlet", + "version": "1.0.0-alpha1.1", + "description": "Local-first drafting app.", + "author": "sreekarnv", + "homepage": "https://github.com/sreekarnv/draftlet", "private": true, "license": "AGPL-3.0-only", "type": "module", "main": "dist-electron/main.js", "scripts": { - "dev": "vp dev", - "build": "tsc && vp build && electron-builder", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "dev": "pnpm kill:port && vp dev", + "kill:port": "fuser -k 8000/tcp || true", + "build": "uv --project ../api run package-runtime && tsc && vp build && electron-builder", + "typecheck": "tsc --noEmit", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "format": "prettier . --write", + "format:check": "prettier . --check", "preview": "vp preview", "contracts:pull": "curl -fsS http://127.0.0.1:8000/openapi.json -o openapi.json", - "contracts:gen": "pnpm contracts:pull && openapi-typescript openapi.json -o src/lib/contracts.generated.ts", - "contracts:check": "pnpm contracts:gen && git diff --exit-code src/lib/contracts.generated.ts" + "contracts:gen": "pnpm contracts:pull && openapi-typescript openapi.json -o src/lib/contracts.generated.ts" }, "dependencies": { "@fontsource-variable/inter": "^5.2.8", @@ -28,32 +36,33 @@ "react-hook-form": "^7.81.0", "react-resizable-panels": "^4.12.1", "react-router": "^8.1.0", - "shadcn": "^4.13.0", "tailwind-merge": "^3.6.0", "tw-animate-css": "^1.4.0", - "zod": "^4.4.3", - "zustand": "^5.0.14" + "zod": "^4.4.3" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@tailwindcss/vite": "^4.3.2", "@types/qrcode": "^1.5.6", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.0", - "@typescript-eslint/eslint-plugin": "^7.1.1", - "@typescript-eslint/parser": "^7.1.1", "@vitejs/plugin-react": "^4.2.1", "electron": "^30.0.1", "electron-builder": "^24.13.3", - "esbuild": "^0.21.5", - "eslint": "^8.57.0", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.4.5", + "eslint": "^10.7.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.3", + "globals": "^17.7.0", "openapi-typescript": "^7.13.0", + "prettier": "^3.9.6", + "prettier-plugin-tailwindcss": "^0.8.1", + "shadcn": "^4.14.0", "tailwindcss": "^4.3.2", "typescript": "^5.2.2", + "typescript-eslint": "^8.65.0", "vite": "catalog:", "vite-plugin-electron": "^0.28.6", - "vite-plugin-electron-renderer": "^0.14.7", "vite-plus": "catalog:" }, "devEngines": { diff --git a/desktop/pnpm-lock.yaml b/desktop/pnpm-lock.yaml index 863de68..29c47b9 100644 --- a/desktop/pnpm-lock.yaml +++ b/desktop/pnpm-lock.yaml @@ -255,9 +255,6 @@ importers: react-router: specifier: ^8.1.0 version: 8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - shadcn: - specifier: ^4.13.0 - version: 4.13.0(supports-color@10.2.2)(typescript@5.9.3) tailwind-merge: specifier: ^3.6.0 version: 3.6.0 @@ -267,13 +264,13 @@ importers: zod: specifier: ^4.4.3 version: 4.4.3 - zustand: - specifier: ^5.0.14 - version: 5.0.14(@types/react@19.2.17)(react@19.2.7) devDependencies: + '@eslint/js': + specifier: ^10.0.1 + version: 10.0.1(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2)) '@tailwindcss/vite': specifier: ^4.3.2 - version: 4.3.2(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3)) + version: 4.3.2(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3)) '@types/qrcode': specifier: ^1.5.6 version: 1.5.6 @@ -283,54 +280,60 @@ importers: '@types/react-dom': specifier: ^19.2.0 version: 19.2.3(@types/react@19.2.17) - '@typescript-eslint/eslint-plugin': - specifier: ^7.1.1 - version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3))(eslint@8.57.1(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) - '@typescript-eslint/parser': - specifier: ^7.1.1 - version: 7.18.0(eslint@8.57.1(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) '@vitejs/plugin-react': specifier: ^4.2.1 - version: 4.7.0(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3)) + version: 4.7.0(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3)) electron: specifier: ^30.0.1 version: 30.5.1(supports-color@10.2.2) electron-builder: specifier: ^24.13.3 version: 24.13.3(electron-builder-squirrel-windows@24.13.3) - esbuild: - specifier: ^0.21.5 - version: 0.21.5 eslint: - specifier: ^8.57.0 - version: 8.57.1(supports-color@10.2.2) + specifier: ^10.7.0 + version: 10.7.0(jiti@2.7.0)(supports-color@10.2.2) + eslint-config-prettier: + specifier: ^10.1.8 + version: 10.1.8(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2)) eslint-plugin-react-hooks: - specifier: ^4.6.0 - version: 4.6.2(eslint@8.57.1(supports-color@10.2.2)) + specifier: ^7.1.1 + version: 7.1.1(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2)) eslint-plugin-react-refresh: - specifier: ^0.4.5 - version: 0.4.26(eslint@8.57.1(supports-color@10.2.2)) + specifier: ^0.5.3 + version: 0.5.3(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2)) + globals: + specifier: ^17.7.0 + version: 17.7.0 openapi-typescript: specifier: ^7.13.0 version: 7.13.0(typescript@5.9.3) + prettier: + specifier: ^3.9.6 + version: 3.9.6 + prettier-plugin-tailwindcss: + specifier: ^0.8.1 + version: 0.8.1(prettier@3.9.6) + shadcn: + specifier: ^4.14.0 + version: 4.14.0(supports-color@10.2.2)(typescript@5.9.3) tailwindcss: specifier: ^4.3.2 version: 4.3.2 typescript: specifier: ^5.2.2 version: 5.9.3 + typescript-eslint: + specifier: ^8.65.0 + version: 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) vite: specifier: npm:@voidzero-dev/vite-plus-core@0.2.2 - version: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3)' vite-plugin-electron: specifier: ^0.28.6 version: 0.28.8(vite-plugin-electron-renderer@0.14.7) - vite-plugin-electron-renderer: - specifier: ^0.14.7 - version: 0.14.7 vite-plus: specifier: 'catalog:' - version: 0.2.2(@types/node@26.1.0)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3))(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3) + version: 0.2.2(@types/node@26.1.0)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3))(jiti@2.7.0)(typescript@5.9.3) packages: @@ -518,144 +521,6 @@ packages: resolution: {integrity: sha512-kbgXxyEauPJiQQUNG2VgUeyfQNFk6hBF11ISN2PNI6agUgPl55pv4eQmaqHzTAzchBvqZ2tQuRVaPStGf0mxGw==} engines: {node: '>=8.6'} - '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -666,13 +531,34 @@ packages: resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/eslintrc@2.1.4': - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/js@8.57.1': - resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint/config-helpers@0.6.0': + resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.7.2': + resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@floating-ui/core@1.7.5': resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} @@ -703,18 +589,25 @@ packages: peerDependencies: react-hook-form: ^7.55.0 - '@humanwhocodes/config-array@0.13.0': - resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - '@humanwhocodes/object-schema@2.0.3': - resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - deprecated: Use @eslint/object-schema instead + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} @@ -1928,6 +1821,9 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} @@ -1937,6 +1833,9 @@ packages: '@types/http-cache-semantics@4.2.0': resolution: {integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/keyv@3.1.4': resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} @@ -1975,66 +1874,64 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@typescript-eslint/eslint-plugin@7.18.0': - resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/eslint-plugin@8.65.0': + resolution: {integrity: sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^7.0.0 - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/parser': ^8.65.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@7.18.0': - resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/parser@8.65.0': + resolution: {integrity: sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/scope-manager@7.18.0': - resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} - engines: {node: ^18.18.0 || >=20.0.0} + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@7.18.0': - resolution: {integrity: sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/project-service@8.65.0': + resolution: {integrity: sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@7.18.0': - resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/scope-manager@8.65.0': + resolution: {integrity: sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@7.18.0': - resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/tsconfig-utils@8.65.0': + resolution: {integrity: sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@7.18.0': - resolution: {integrity: sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/type-utils@8.65.0': + resolution: {integrity: sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@7.18.0': - resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/types@8.65.0': + resolution: {integrity: sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ungap/structured-clone@1.3.2': - resolution: {integrity: sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==} + '@typescript-eslint/typescript-estree@8.65.0': + resolution: {integrity: sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.65.0': + resolution: {integrity: sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.65.0': + resolution: {integrity: sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vitejs/plugin-react@4.7.0': resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} @@ -2299,10 +2196,6 @@ packages: aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - assert-plus@1.0.0: resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} engines: {node: '>=0.8'} @@ -2710,10 +2603,6 @@ packages: dir-compare@3.3.0: resolution: {integrity: sha512-J7/et3WlGUCxjdnD3HAAzQ6nsnc0WL6DD7WcwJb7c39iH1+AWfg+9OqzJNaI6PkBwBvm1mhZNL9iY/nRiZXlPg==} - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - dmg-builder@24.13.3: resolution: {integrity: sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==} @@ -2723,10 +2612,6 @@ packages: os: [darwin] hasBin: true - doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} @@ -2835,11 +2720,6 @@ packages: es6-error@4.1.1: resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} - esbuild@0.21.5: - resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} - engines: {node: '>=12'} - hasBin: true - escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -2851,34 +2731,48 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-plugin-react-hooks@4.6.2: - resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} - engines: {node: '>=10'} + eslint-config-prettier@10.1.8: + resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} + hasBin: true peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + eslint: '>=7.0.0' - eslint-plugin-react-refresh@0.4.26: - resolution: {integrity: sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==} + eslint-plugin-react-hooks@7.1.1: + resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} + engines: {node: '>=18'} peerDependencies: - eslint: '>=8.40' + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-plugin-react-refresh@0.5.3: + resolution: {integrity: sha512-5EMmLCV98Pi4o/f/3DP/v/tNqLHMIc9I8LKClNDWhZ9JTho89/kQcitCXQBMG7sAfVRK0Ie3T2EDOzp1YXYiVA==} + peerDependencies: + eslint: ^9 || ^10 + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint@8.57.1: - resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.7.0: + resolution: {integrity: sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true - espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} @@ -2928,8 +2822,8 @@ packages: resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} engines: {node: '>=12.0.0'} - express-rate-limit@8.5.2: - resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==} + express-rate-limit@8.6.0: + resolution: {integrity: sha512-XKJXDsASUOo0LLtFwW5hCcQGH0N4WQc/Rn8/Pvoia+TJFOkkFPvrtW9lZOeeNcxQJspvOIERMwiRLsVFlhHEkA==} engines: {node: '>= 16'} peerDependencies: express: '>= 4.11' @@ -2960,8 +2854,8 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-uri@3.1.3: - resolution: {integrity: sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==} + fast-uri@3.1.4: + resolution: {integrity: sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==} fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -2982,9 +2876,9 @@ packages: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} engines: {node: '>=18'} - file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} filelist@1.0.6: resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==} @@ -3009,9 +2903,9 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} flatted@3.4.2: resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} @@ -3130,18 +3024,14 @@ packages: resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} engines: {node: '>=10.0'} - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} + globals@17.7.0: + resolution: {integrity: sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==} + engines: {node: '>=18'} globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -3153,9 +3043,6 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -3175,8 +3062,14 @@ packages: resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} - hono@4.12.28: - resolution: {integrity: sha512-YwUvVpSF7m1yOblFPrU3Hbo8XhPheBoiyfGuII6z19LnOr6JpDnyyp7LFNrfV56wS8tpvtBFGRISHN02pDdLOA==} + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + + hono@4.12.31: + resolution: {integrity: sha512-zJIHFrl6bq3RDd2YusFNCDlM8qUprxKswyi/OPzPyzKDdyBXDqWx8bZlZ7R+saTdSTatUmb3O7K4SspGPaEOQg==} engines: {node: '>=16.9.0'} hosted-git-info@4.1.0: @@ -3234,6 +3127,10 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.6: + resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==} + engines: {node: '>= 4'} + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -3315,10 +3212,6 @@ packages: resolution: {integrity: sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==} engines: {node: '>=12'} - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} @@ -3384,8 +3277,8 @@ packages: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true - jose@6.2.3: - resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + jose@6.2.4: + resolution: {integrity: sha512-N8acGzVsQy6M/fjFcxtysNc4Q379TcM5dM/qKkNtsHFji88yANnXTr7BLeP75iPnFwBfQzM/jg2BZ9+HZrHCZA==} js-levenshtein@1.1.6: resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} @@ -3565,9 +3458,6 @@ packages: lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - lodash.union@4.6.0: resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} @@ -3921,10 +3811,6 @@ packages: path-to-regexp@8.4.2: resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -3982,6 +3868,66 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prettier-plugin-tailwindcss@0.8.1: + resolution: {integrity: sha512-iaFMYqDsE4ffdDkn5qup0j5f2aCEBFZrdrZnvu9QKTlWx/iGPeQ4HHu7b7fCPMxeo9nwQBiOAh2nSypdFYWJkw==} + engines: {node: '>=20.19'} + peerDependencies: + '@ianvs/prettier-plugin-sort-imports': '*' + '@prettier/plugin-hermes': '*' + '@prettier/plugin-oxc': '*' + '@prettier/plugin-pug': '*' + '@shopify/prettier-plugin-liquid': '*' + '@trivago/prettier-plugin-sort-imports': '*' + '@zackad/prettier-plugin-twig': '*' + prettier: ^3.0 + prettier-plugin-astro: '*' + prettier-plugin-css-order: '*' + prettier-plugin-jsdoc: '*' + prettier-plugin-marko: '*' + prettier-plugin-multiline-arrays: '*' + prettier-plugin-organize-attributes: '*' + prettier-plugin-organize-imports: '*' + prettier-plugin-sort-imports: '*' + prettier-plugin-svelte: '*' + peerDependenciesMeta: + '@ianvs/prettier-plugin-sort-imports': + optional: true + '@prettier/plugin-hermes': + optional: true + '@prettier/plugin-oxc': + optional: true + '@prettier/plugin-pug': + optional: true + '@shopify/prettier-plugin-liquid': + optional: true + '@trivago/prettier-plugin-sort-imports': + optional: true + '@zackad/prettier-plugin-twig': + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-css-order: + optional: true + prettier-plugin-jsdoc: + optional: true + prettier-plugin-marko: + optional: true + prettier-plugin-multiline-arrays: + optional: true + prettier-plugin-organize-attributes: + optional: true + prettier-plugin-organize-imports: + optional: true + prettier-plugin-sort-imports: + optional: true + prettier-plugin-svelte: + optional: true + + prettier@3.9.6: + resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} + engines: {node: '>=14'} + hasBin: true + pretty-format@27.5.1: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -4172,11 +4118,6 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - roarr@2.15.4: resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} engines: {node: '>=8.0'} @@ -4241,8 +4182,8 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - shadcn@4.13.0: - resolution: {integrity: sha512-5fuJ4jI/GcPeA/iTL4cJivCZuYQGXz/N3bIzyd+Gd/FM6xUCy2MxGG+LaDQuw2cjNy9zGPSFPTEmI048UwPTZA==} + shadcn@4.14.0: + resolution: {integrity: sha512-WSSMmB9ERauwy0SQwE0NlSfeaoBANHtzgSjAfoEsIHpRQwVkQBiiLV8Y9yAYOj9Wf3mbCIdQt3oLGzkH7NQFiQ==} engines: {node: '>=20.18.1'} hasBin: true @@ -4291,10 +4232,6 @@ packages: sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - slice-ansi@3.0.0: resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} engines: {node: '>=8'} @@ -4377,10 +4314,6 @@ packages: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - sumchecker@3.0.1: resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} engines: {node: '>= 8.0'} @@ -4393,9 +4326,9 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} - systeminformation@5.31.14: - resolution: {integrity: sha512-nefRpMCsAI4m71/6JHH//KPaP/d5nTuRVxEtQ7N7SlBrX18DAcC+5Z1JKZYeN9Iw49qMx95BTo/gBMk3Y2H6+g==} - engines: {node: '>=8.0.0'} + systeminformation@5.33.0: + resolution: {integrity: sha512-0LYSL01CCbjVeJG7iXI8fUCFU76zMjzbHd/EU3or4QpSFYCLMgslR11prwHuA3siz5jmOkqoLhjgOyDRmXBKmA==} + engines: {node: '>=10.0.0'} os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] hasBin: true @@ -4421,9 +4354,6 @@ packages: temp-file@3.4.0: resolution: {integrity: sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==} - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -4468,11 +4398,11 @@ packages: truncate-utf8-bytes@1.0.2: resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==} - ts-api-utils@1.4.3: - resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} - engines: {node: '>=16'} + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} peerDependencies: - typescript: '>=4.2.0' + typescript: '>=4.8.4' ts-morph@26.0.0: resolution: {integrity: sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==} @@ -4495,10 +4425,6 @@ packages: resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} engines: {node: '>=10'} - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - type-fest@4.41.0: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} @@ -4507,6 +4433,13 @@ packages: resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} engines: {node: '>= 18'} + typescript-eslint@8.65.0: + resolution: {integrity: sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -4749,8 +4682,8 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - yocto-spinner@1.2.1: - resolution: {integrity: sha512-9cbFWLhbiZp+820O4pkHGNncI7+MrUGzBOjw8NMG+ewsY+aG0DdEXnr19Smxao32YOjLZRMdn1UtaxcrXOYOIg==} + yocto-spinner@1.2.2: + resolution: {integrity: sha512-DODGl1wJjA/s5pnJFKau9lIYHT81lnhob1i3e1TjxZRxEhWRKl74nTbWE6H5KlkViQQTo/Z29YFdxzTZAMY3ng==} engines: {node: '>=18.19'} yoctocolors@2.1.2: @@ -4766,30 +4699,18 @@ packages: peerDependencies: zod: ^3.25.28 || ^4 + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} zod@4.4.3: resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} - zustand@5.0.14: - resolution: {integrity: sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==} - engines: {node: '>=12.20.0'} - peerDependencies: - '@types/react': '>=18.0.0' - immer: '>=9.0.6' - react: '>=18.0.0' - use-sync-external-store: '>=1.2.0' - peerDependenciesMeta: - '@types/react': - optional: true - immer: - optional: true - react: - optional: true - use-sync-external-store: - optional: true - snapshots: 7zip-bin@5.2.0: {} @@ -5013,10 +4934,10 @@ snapshots: object-treeify: 1.1.33 open: 8.4.2 picomatch: 4.0.5 - systeminformation: 5.31.14 + systeminformation: 5.33.0 undici: 7.28.0 which: 4.0.0 - yocto-spinner: 1.2.1 + yocto-spinner: 1.2.2 '@dotenvx/primitives@0.8.0': {} @@ -5071,97 +4992,39 @@ snapshots: transitivePeerDependencies: - supports-color - '@esbuild/aix-ppc64@0.21.5': - optional: true - - '@esbuild/android-arm64@0.21.5': - optional: true - - '@esbuild/android-arm@0.21.5': - optional: true - - '@esbuild/android-x64@0.21.5': - optional: true - - '@esbuild/darwin-arm64@0.21.5': - optional: true - - '@esbuild/darwin-x64@0.21.5': - optional: true - - '@esbuild/freebsd-arm64@0.21.5': - optional: true - - '@esbuild/freebsd-x64@0.21.5': - optional: true - - '@esbuild/linux-arm64@0.21.5': - optional: true - - '@esbuild/linux-arm@0.21.5': - optional: true - - '@esbuild/linux-ia32@0.21.5': - optional: true - - '@esbuild/linux-loong64@0.21.5': - optional: true - - '@esbuild/linux-mips64el@0.21.5': - optional: true - - '@esbuild/linux-ppc64@0.21.5': - optional: true - - '@esbuild/linux-riscv64@0.21.5': - optional: true - - '@esbuild/linux-s390x@0.21.5': - optional: true - - '@esbuild/linux-x64@0.21.5': - optional: true - - '@esbuild/netbsd-x64@0.21.5': - optional: true - - '@esbuild/openbsd-x64@0.21.5': - optional: true - - '@esbuild/sunos-x64@0.21.5': - optional: true - - '@esbuild/win32-arm64@0.21.5': - optional: true - - '@esbuild/win32-ia32@0.21.5': - optional: true - - '@esbuild/win32-x64@0.21.5': - optional: true - - '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1(supports-color@10.2.2))': + '@eslint-community/eslint-utils@4.9.1(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2))': dependencies: - eslint: 8.57.1(supports-color@10.2.2) + eslint: 10.7.0(jiti@2.7.0)(supports-color@10.2.2) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/eslintrc@2.1.4(supports-color@10.2.2)': + '@eslint/config-array@0.23.5(supports-color@10.2.2)': dependencies: - ajv: 6.15.0 + '@eslint/object-schema': 3.0.5 debug: 4.4.3(supports-color@10.2.2) - espree: 9.6.1 - globals: 13.24.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.3.0 - minimatch: 3.1.5 - strip-json-comments: 3.1.1 + minimatch: 10.2.5 transitivePeerDependencies: - supports-color - '@eslint/js@8.57.1': {} + '@eslint/config-helpers@0.6.0': + dependencies: + '@eslint/core': 1.2.1 + + '@eslint/core@1.2.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/js@10.0.1(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2))': + optionalDependencies: + eslint: 10.7.0(jiti@2.7.0)(supports-color@10.2.2) + + '@eslint/object-schema@3.0.5': {} + + '@eslint/plugin-kit@0.7.2': + dependencies: + '@eslint/core': 1.2.1 + levn: 0.4.1 '@floating-ui/core@1.7.5': dependencies: @@ -5182,26 +5045,30 @@ snapshots: '@fontsource-variable/inter@5.2.8': {} - '@hono/node-server@1.19.14(hono@4.12.28)': + '@hono/node-server@1.19.14(hono@4.12.31)': dependencies: - hono: 4.12.28 + hono: 4.12.31 '@hookform/resolvers@5.4.0(react-hook-form@7.81.0(react@19.2.7))': dependencies: '@standard-schema/utils': 0.3.0 react-hook-form: 7.81.0(react@19.2.7) - '@humanwhocodes/config-array@0.13.0(supports-color@10.2.2)': + '@humanfs/core@0.19.2': dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.3(supports-color@10.2.2) - minimatch: 3.1.5 - transitivePeerDependencies: - - supports-color + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} '@humanwhocodes/module-importer@1.0.1': {} - '@humanwhocodes/object-schema@2.0.3': {} + '@humanwhocodes/retry@0.4.3': {} '@isaacs/cliui@8.0.2': dependencies: @@ -5246,7 +5113,7 @@ snapshots: '@modelcontextprotocol/sdk@1.29.0(supports-color@10.2.2)(zod@3.25.76)': dependencies: - '@hono/node-server': 1.19.14(hono@4.12.28) + '@hono/node-server': 1.19.14(hono@4.12.31) ajv: 8.20.0 ajv-formats: 3.0.1(ajv@8.20.0) content-type: 1.0.5 @@ -5255,9 +5122,9 @@ snapshots: eventsource: 3.0.7 eventsource-parser: 3.1.0 express: 5.2.1(supports-color@10.2.2) - express-rate-limit: 8.5.2(express@5.2.1(supports-color@10.2.2)) - hono: 4.12.28 - jose: 6.2.3 + express-rate-limit: 8.6.0(express@5.2.1(supports-color@10.2.2))(supports-color@10.2.2) + hono: 4.12.31 + jose: 6.2.4 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 raw-body: 3.0.2 @@ -6267,12 +6134,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.3.2 '@tailwindcss/oxide-win32-x64-msvc': 4.3.2 - '@tailwindcss/vite@4.3.2(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3))': + '@tailwindcss/vite@4.3.2(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3))': dependencies: '@tailwindcss/node': 4.3.2 '@tailwindcss/oxide': 4.3.2 tailwindcss: 4.3.2 - vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3)' + vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3)' '@tanstack/query-core@5.101.2': {} @@ -6345,6 +6212,8 @@ snapshots: '@types/deep-eql@4.0.2': {} + '@types/esrecurse@4.3.1': {} + '@types/estree@1.0.9': {} '@types/fs-extra@9.0.13': @@ -6353,6 +6222,8 @@ snapshots: '@types/http-cache-semantics@4.2.0': {} + '@types/json-schema@7.0.15': {} + '@types/keyv@3.1.4': dependencies: '@types/node': 26.1.0 @@ -6399,90 +6270,98 @@ snapshots: '@types/node': 26.1.0 optional: true - '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3))(eslint@8.57.1(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3))(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.1(supports-color@10.2.2))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 7.18.0 - eslint: 8.57.1(supports-color@10.2.2) - graphemer: 1.4.0 - ignore: 5.3.2 + '@typescript-eslint/parser': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.65.0 + '@typescript-eslint/type-utils': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) + '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.65.0 + eslint: 10.7.0(jiti@2.7.0)(supports-color@10.2.2) + ignore: 7.0.6 natural-compare: 1.4.0 - ts-api-utils: 1.4.3(typescript@5.9.3) - optionalDependencies: + ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.18.0(eslint@8.57.1(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)': + '@typescript-eslint/parser@8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(supports-color@10.2.2)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 7.18.0 + '@typescript-eslint/scope-manager': 8.65.0 + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.65.0 + debug: 4.4.3(supports-color@10.2.2) + eslint: 10.7.0(jiti@2.7.0)(supports-color@10.2.2) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.65.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.9.3) + '@typescript-eslint/types': 8.65.0 debug: 4.4.3(supports-color@10.2.2) - eslint: 8.57.1(supports-color@10.2.2) - optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@7.18.0': + '@typescript-eslint/scope-manager@8.65.0': dependencies: - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/visitor-keys': 7.18.0 + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/visitor-keys': 8.65.0 - '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.65.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/typescript-estree': 7.18.0(supports-color@10.2.2)(typescript@5.9.3) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.1(supports-color@10.2.2))(typescript@5.9.3) + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2))(typescript@5.9.3) debug: 4.4.3(supports-color@10.2.2) - eslint: 8.57.1(supports-color@10.2.2) - ts-api-utils: 1.4.3(typescript@5.9.3) - optionalDependencies: + eslint: 10.7.0(jiti@2.7.0)(supports-color@10.2.2) + ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@7.18.0': {} + '@typescript-eslint/types@8.65.0': {} - '@typescript-eslint/typescript-estree@7.18.0(supports-color@10.2.2)(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.65.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/visitor-keys': 7.18.0 + '@typescript-eslint/project-service': 8.65.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.9.3) + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/visitor-keys': 8.65.0 debug: 4.4.3(supports-color@10.2.2) - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.9 + minimatch: 10.2.5 semver: 7.8.5 - ts-api-utils: 1.4.3(typescript@5.9.3) - optionalDependencies: + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@7.18.0(eslint@8.57.1(supports-color@10.2.2))(typescript@5.9.3)': + '@typescript-eslint/utils@8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1(supports-color@10.2.2)) - '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(supports-color@10.2.2)(typescript@5.9.3) - eslint: 8.57.1(supports-color@10.2.2) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2)) + '@typescript-eslint/scope-manager': 8.65.0 + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.9.3) + eslint: 10.7.0(jiti@2.7.0)(supports-color@10.2.2) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - - typescript - '@typescript-eslint/visitor-keys@7.18.0': + '@typescript-eslint/visitor-keys@8.65.0': dependencies: - '@typescript-eslint/types': 7.18.0 - eslint-visitor-keys: 3.4.3 + '@typescript-eslint/types': 8.65.0 + eslint-visitor-keys: 5.0.1 - '@ungap/structured-clone@1.3.2': {} - - '@vitejs/plugin-react@4.7.0(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3))': + '@vitejs/plugin-react@4.7.0(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3))': dependencies: '@babel/core': 7.29.7 '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7) @@ -6490,32 +6369,32 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3)' + vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3)' transitivePeerDependencies: - supports-color - '@vitest/browser-preview@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3))(vitest@4.1.9)': + '@vitest/browser-preview@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3))(vitest@4.1.9)': dependencies: '@testing-library/dom': 10.4.1 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) - '@vitest/browser': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3))(vitest@4.1.9) - vitest: 4.1.9(@types/node@26.1.0)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3)) + '@vitest/browser': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3))(vitest@4.1.9) + vitest: 4.1.9(@types/node@26.1.0)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3)) transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite - '@vitest/browser@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3))(vitest@4.1.9)': + '@vitest/browser@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3))(vitest@4.1.9)': dependencies: '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3)) + '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3)) '@vitest/utils': 4.1.9 magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.0 - vitest: 4.1.9(@types/node@26.1.0)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3)) + vitest: 4.1.9(@types/node@26.1.0)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3)) ws: 8.21.0 transitivePeerDependencies: - bufferutil @@ -6532,13 +6411,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3))': + '@vitest/mocker@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3))': dependencies: '@vitest/spy': 4.1.9 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3)' + vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3)' '@vitest/pretty-format@4.1.9': dependencies: @@ -6564,7 +6443,7 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 - '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3)': + '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3)': dependencies: '@oxc-project/runtime': 0.138.0 '@oxc-project/types': 0.138.0 @@ -6572,7 +6451,6 @@ snapshots: postcss: 8.5.16 optionalDependencies: '@types/node': 26.1.0 - esbuild: 0.21.5 fsevents: 2.3.3 jiti: 2.7.0 typescript: 5.9.3 @@ -6644,7 +6522,7 @@ snapshots: ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.3 + fast-uri: 3.1.4 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -6744,8 +6622,6 @@ snapshots: dependencies: dequal: 2.0.3 - array-union@2.1.0: {} - assert-plus@1.0.0: optional: true @@ -7129,10 +7005,6 @@ snapshots: buffer-equal: 1.0.1 minimatch: 3.1.5 - dir-glob@3.0.1: - dependencies: - path-type: 4.0.0 - dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3): dependencies: app-builder-lib: 24.13.3(dmg-builder@24.13.3)(electron-builder-squirrel-windows@24.13.3) @@ -7159,10 +7031,6 @@ snapshots: verror: 1.10.1 optional: true - doctrine@3.0.0: - dependencies: - esutils: 2.0.3 - dom-accessibility-api@0.5.16: {} dot-prop@6.0.1: @@ -7288,101 +7156,84 @@ snapshots: es6-error@4.1.1: optional: true - esbuild@0.21.5: - optionalDependencies: - '@esbuild/aix-ppc64': 0.21.5 - '@esbuild/android-arm': 0.21.5 - '@esbuild/android-arm64': 0.21.5 - '@esbuild/android-x64': 0.21.5 - '@esbuild/darwin-arm64': 0.21.5 - '@esbuild/darwin-x64': 0.21.5 - '@esbuild/freebsd-arm64': 0.21.5 - '@esbuild/freebsd-x64': 0.21.5 - '@esbuild/linux-arm': 0.21.5 - '@esbuild/linux-arm64': 0.21.5 - '@esbuild/linux-ia32': 0.21.5 - '@esbuild/linux-loong64': 0.21.5 - '@esbuild/linux-mips64el': 0.21.5 - '@esbuild/linux-ppc64': 0.21.5 - '@esbuild/linux-riscv64': 0.21.5 - '@esbuild/linux-s390x': 0.21.5 - '@esbuild/linux-x64': 0.21.5 - '@esbuild/netbsd-x64': 0.21.5 - '@esbuild/openbsd-x64': 0.21.5 - '@esbuild/sunos-x64': 0.21.5 - '@esbuild/win32-arm64': 0.21.5 - '@esbuild/win32-ia32': 0.21.5 - '@esbuild/win32-x64': 0.21.5 - escalade@3.2.0: {} escape-html@1.0.3: {} escape-string-regexp@4.0.0: {} - eslint-plugin-react-hooks@4.6.2(eslint@8.57.1(supports-color@10.2.2)): + eslint-config-prettier@10.1.8(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2)): dependencies: - eslint: 8.57.1(supports-color@10.2.2) + eslint: 10.7.0(jiti@2.7.0)(supports-color@10.2.2) - eslint-plugin-react-refresh@0.4.26(eslint@8.57.1(supports-color@10.2.2)): + eslint-plugin-react-hooks@7.1.1(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2)): dependencies: - eslint: 8.57.1(supports-color@10.2.2) + '@babel/core': 7.29.7 + '@babel/parser': 7.29.7 + eslint: 10.7.0(jiti@2.7.0)(supports-color@10.2.2) + hermes-parser: 0.25.1 + zod: 4.4.3 + zod-validation-error: 4.0.2(zod@4.4.3) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react-refresh@0.5.3(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2)): + dependencies: + eslint: 10.7.0(jiti@2.7.0)(supports-color@10.2.2) - eslint-scope@7.2.2: + eslint-scope@9.1.2: dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.9 esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint@8.57.1(supports-color@10.2.2): + eslint-visitor-keys@5.0.1: {} + + eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1(supports-color@10.2.2)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2)) '@eslint-community/regexpp': 4.12.2 - '@eslint/eslintrc': 2.1.4(supports-color@10.2.2) - '@eslint/js': 8.57.1 - '@humanwhocodes/config-array': 0.13.0(supports-color@10.2.2) + '@eslint/config-array': 0.23.5(supports-color@10.2.2) + '@eslint/config-helpers': 0.6.0 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.2 + '@humanfs/node': 0.16.8 '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.3.2 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 ajv: 6.15.0 - chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.3(supports-color@10.2.2) - doctrine: 3.0.0 escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 esquery: 1.7.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 + file-entry-cache: 8.0.0 find-up: 5.0.0 glob-parent: 6.0.2 - globals: 13.24.0 - graphemer: 1.4.0 ignore: 5.3.2 imurmurhash: 0.1.4 is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.3.0 json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.5 + minimatch: 10.2.5 natural-compare: 1.4.0 optionator: 0.9.4 - strip-ansi: 6.0.1 - text-table: 0.2.0 + optionalDependencies: + jiti: 2.7.0 transitivePeerDependencies: - supports-color - espree@9.6.1: + espree@11.2.0: dependencies: acorn: 8.17.0 acorn-jsx: 5.3.2(acorn@8.17.0) - eslint-visitor-keys: 3.4.3 + eslint-visitor-keys: 5.0.1 esprima@4.0.1: {} @@ -7439,10 +7290,13 @@ snapshots: expect-type@1.4.0: {} - express-rate-limit@8.5.2(express@5.2.1(supports-color@10.2.2)): + express-rate-limit@8.6.0(express@5.2.1(supports-color@10.2.2))(supports-color@10.2.2): dependencies: + debug: 4.4.3(supports-color@10.2.2) express: 5.2.1(supports-color@10.2.2) ip-address: 10.2.0 + transitivePeerDependencies: + - supports-color express@5.2.1(supports-color@10.2.2): dependencies: @@ -7504,7 +7358,7 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-uri@3.1.3: {} + fast-uri@3.1.4: {} fastq@1.20.1: dependencies: @@ -7522,9 +7376,9 @@ snapshots: dependencies: is-unicode-supported: 2.1.0 - file-entry-cache@6.0.1: + file-entry-cache@8.0.0: dependencies: - flat-cache: 3.2.0 + flat-cache: 4.0.1 filelist@1.0.6: dependencies: @@ -7559,11 +7413,10 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - flat-cache@3.2.0: + flat-cache@4.0.1: dependencies: flatted: 3.4.2 keyv: 4.5.4 - rimraf: 3.0.2 flatted@3.4.2: {} @@ -7699,9 +7552,7 @@ snapshots: serialize-error: 7.0.1 optional: true - globals@13.24.0: - dependencies: - type-fest: 0.20.2 + globals@17.7.0: {} globalthis@1.0.4: dependencies: @@ -7709,15 +7560,6 @@ snapshots: gopd: 1.2.0 optional: true - globby@11.1.0: - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.3 - ignore: 5.3.2 - merge2: 1.4.1 - slash: 3.0.0 - gopd@1.2.0: {} got@11.8.6: @@ -7736,8 +7578,6 @@ snapshots: graceful-fs@4.2.11: {} - graphemer@1.4.0: {} - has-flag@4.0.0: {} has-property-descriptors@1.0.2: @@ -7755,7 +7595,13 @@ snapshots: dependencies: function-bind: 1.1.2 - hono@4.12.28: {} + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + hono@4.12.31: {} hosted-git-info@4.1.0: dependencies: @@ -7820,6 +7666,8 @@ snapshots: ignore@5.3.2: {} + ignore@7.0.6: {} + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -7872,8 +7720,6 @@ snapshots: is-obj@3.0.0: {} - is-path-inside@3.0.3: {} - is-plain-obj@4.1.0: {} is-promise@4.0.0: {} @@ -7920,7 +7766,7 @@ snapshots: jiti@2.7.0: {} - jose@6.2.3: {} + jose@6.2.4: {} js-levenshtein@1.1.6: {} @@ -8056,8 +7902,6 @@ snapshots: lodash.isplainobject@4.0.6: {} - lodash.merge@4.6.2: {} - lodash.union@4.6.0: {} lodash@4.18.1: {} @@ -8267,7 +8111,7 @@ snapshots: string-width: 7.2.0 strip-ansi: 7.2.0 - oxfmt@0.57.0(vite-plus@0.2.2(@types/node@26.1.0)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3))(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3)): + oxfmt@0.57.0(vite-plus@0.2.2(@types/node@26.1.0)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3))(jiti@2.7.0)(typescript@5.9.3)): dependencies: tinypool: 2.1.0 optionalDependencies: @@ -8290,7 +8134,7 @@ snapshots: '@oxfmt/binding-win32-arm64-msvc': 0.57.0 '@oxfmt/binding-win32-ia32-msvc': 0.57.0 '@oxfmt/binding-win32-x64-msvc': 0.57.0 - vite-plus: 0.2.2(@types/node@26.1.0)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3))(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3) + vite-plus: 0.2.2(@types/node@26.1.0)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3))(jiti@2.7.0)(typescript@5.9.3) oxlint-tsgolint@0.24.0: optionalDependencies: @@ -8301,7 +8145,7 @@ snapshots: '@oxlint-tsgolint/win32-arm64': 0.24.0 '@oxlint-tsgolint/win32-x64': 0.24.0 - oxlint@1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@26.1.0)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3))(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3)): + oxlint@1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@26.1.0)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3))(jiti@2.7.0)(typescript@5.9.3)): optionalDependencies: '@oxlint/binding-android-arm-eabi': 1.72.0 '@oxlint/binding-android-arm64': 1.72.0 @@ -8323,7 +8167,7 @@ snapshots: '@oxlint/binding-win32-ia32-msvc': 1.72.0 '@oxlint/binding-win32-x64-msvc': 1.72.0 oxlint-tsgolint: 0.24.0 - vite-plus: 0.2.2(@types/node@26.1.0)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3))(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3) + vite-plus: 0.2.2(@types/node@26.1.0)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3))(jiti@2.7.0)(typescript@5.9.3) p-cancelable@2.1.1: {} @@ -8391,8 +8235,6 @@ snapshots: path-to-regexp@8.4.2: {} - path-type@4.0.0: {} - pathe@2.0.3: {} pend@1.2.0: {} @@ -8436,6 +8278,12 @@ snapshots: prelude-ls@1.2.1: {} + prettier-plugin-tailwindcss@0.8.1(prettier@3.9.6): + dependencies: + prettier: 3.9.6 + + prettier@3.9.6: {} + pretty-format@27.5.1: dependencies: ansi-regex: 5.0.1 @@ -8673,10 +8521,6 @@ snapshots: reusify@1.1.0: {} - rimraf@3.0.2: - dependencies: - glob: 7.2.3 - roarr@2.15.4: dependencies: boolean: 3.2.0 @@ -8758,7 +8602,7 @@ snapshots: setprototypeof@1.2.0: {} - shadcn@4.13.0(supports-color@10.2.2)(typescript@5.9.3): + shadcn@4.14.0(supports-color@10.2.2)(typescript@5.9.3): dependencies: '@babel/core': 7.29.7 '@babel/parser': 7.29.7 @@ -8850,8 +8694,6 @@ snapshots: sisteransi@1.0.5: {} - slash@3.0.0: {} - slice-ansi@3.0.0: dependencies: ansi-styles: 4.3.0 @@ -8930,8 +8772,6 @@ snapshots: strip-final-newline@4.0.0: {} - strip-json-comments@3.1.1: {} - sumchecker@3.0.1(supports-color@10.2.2): dependencies: debug: 4.4.3(supports-color@10.2.2) @@ -8944,7 +8784,7 @@ snapshots: dependencies: has-flag: 4.0.0 - systeminformation@5.31.14: {} + systeminformation@5.33.0: {} tailwind-merge@3.6.0: {} @@ -8974,8 +8814,6 @@ snapshots: async-exit-hook: 2.0.1 fs-extra: 10.1.0 - text-table@0.2.0: {} - tiny-invariant@1.3.3: {} tinybench@2.9.0: {} @@ -9009,7 +8847,7 @@ snapshots: dependencies: utf8-byte-length: 1.0.5 - ts-api-utils@1.4.3(typescript@5.9.3): + ts-api-utils@2.5.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -9035,8 +8873,6 @@ snapshots: type-fest@0.13.1: optional: true - type-fest@0.20.2: {} - type-fest@4.41.0: {} type-is@2.1.0: @@ -9045,6 +8881,17 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 + typescript-eslint@8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3))(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) + '@typescript-eslint/parser': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.65.0(eslint@10.7.0(jiti@2.7.0)(supports-color@10.2.2))(typescript@5.9.3) + eslint: 10.7.0(jiti@2.7.0)(supports-color@10.2.2) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + typescript@5.9.3: {} undici-types@6.21.0: {} @@ -9103,30 +8950,31 @@ snapshots: extsprintf: 1.4.1 optional: true - vite-plugin-electron-renderer@0.14.7: {} + vite-plugin-electron-renderer@0.14.7: + optional: true vite-plugin-electron@0.28.8(vite-plugin-electron-renderer@0.14.7): optionalDependencies: vite-plugin-electron-renderer: 0.14.7 - vite-plus@0.2.2(@types/node@26.1.0)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3))(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3): + vite-plus@0.2.2(@types/node@26.1.0)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3))(jiti@2.7.0)(typescript@5.9.3): dependencies: '@oxc-project/types': 0.138.0 '@oxlint/plugins': 1.68.0 - '@vitest/browser': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3))(vitest@4.1.9) - '@vitest/browser-preview': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3))(vitest@4.1.9) + '@vitest/browser': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3))(vitest@4.1.9) + '@vitest/browser-preview': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3))(vitest@4.1.9) '@vitest/expect': 4.1.9 - '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3)) + '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3)) '@vitest/pretty-format': 4.1.9 '@vitest/runner': 4.1.9 '@vitest/snapshot': 4.1.9 '@vitest/spy': 4.1.9 '@vitest/utils': 4.1.9 - '@voidzero-dev/vite-plus-core': 0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3) - oxfmt: 0.57.0(vite-plus@0.2.2(@types/node@26.1.0)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3))(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3)) - oxlint: 1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@26.1.0)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3))(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3)) + '@voidzero-dev/vite-plus-core': 0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3) + oxfmt: 0.57.0(vite-plus@0.2.2(@types/node@26.1.0)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3))(jiti@2.7.0)(typescript@5.9.3)) + oxlint: 1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@26.1.0)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3))(jiti@2.7.0)(typescript@5.9.3)) oxlint-tsgolint: 0.24.0 - vitest: 4.1.9(@types/node@26.1.0)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3)) + vitest: 4.1.9(@types/node@26.1.0)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3)) optionalDependencies: '@voidzero-dev/vite-plus-darwin-arm64': 0.2.2 '@voidzero-dev/vite-plus-darwin-x64': 0.2.2 @@ -9167,10 +9015,10 @@ snapshots: - vite - yaml - vitest@4.1.9(@types/node@26.1.0)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3)): + vitest@4.1.9(@types/node@26.1.0)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3)): dependencies: '@vitest/expect': 4.1.9 - '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3)) + '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3)) '@vitest/pretty-format': 4.1.9 '@vitest/runner': 4.1.9 '@vitest/snapshot': 4.1.9 @@ -9187,11 +9035,11 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3)' + vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3)' why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 26.1.0 - '@vitest/browser-preview': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(esbuild@0.21.5)(jiti@2.7.0)(typescript@5.9.3))(vitest@4.1.9) + '@vitest/browser-preview': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.0)(jiti@2.7.0)(typescript@5.9.3))(vitest@4.1.9) transitivePeerDependencies: - msw @@ -9289,7 +9137,7 @@ snapshots: yocto-queue@0.1.0: {} - yocto-spinner@1.2.1: + yocto-spinner@1.2.2: dependencies: yoctocolors: 2.1.2 @@ -9305,11 +9153,10 @@ snapshots: dependencies: zod: 3.25.76 + zod-validation-error@4.0.2(zod@4.4.3): + dependencies: + zod: 4.4.3 + zod@3.25.76: {} zod@4.4.3: {} - - zustand@5.0.14(@types/react@19.2.17)(react@19.2.7): - optionalDependencies: - '@types/react': 19.2.17 - react: 19.2.7 diff --git a/desktop/public/electron-vite.animate.svg b/desktop/public/electron-vite.animate.svg deleted file mode 100644 index ea3e777..0000000 --- a/desktop/public/electron-vite.animate.svg +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/desktop/public/electron-vite.svg b/desktop/public/electron-vite.svg deleted file mode 100644 index 8a6aefe..0000000 --- a/desktop/public/electron-vite.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/desktop/public/logo.png b/desktop/public/logo.png new file mode 100644 index 0000000..275a617 Binary files /dev/null and b/desktop/public/logo.png differ diff --git a/desktop/public/vite.svg b/desktop/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/desktop/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/desktop/src/components/app-sidebar.tsx b/desktop/src/components/app-sidebar.tsx index 3089ee6..9435ee4 100644 --- a/desktop/src/components/app-sidebar.tsx +++ b/desktop/src/components/app-sidebar.tsx @@ -1,6 +1,6 @@ import type * as React from "react"; import { Link } from "react-router"; -import { Mail, MessageCircle } from "lucide-react"; +import { ChevronRight, Mail, MessageCircle } from "lucide-react"; import draftletLogo from "../../../.github/assets/logo.webp"; import { draftletNavigation } from "@/lib/navigation"; @@ -9,6 +9,11 @@ import { useConversationsQuery } from "@/lib/queries/conversations"; import { useRuntimeStatus } from "@/lib/runtime-status"; import { StatusDot } from "@/components/status-dot"; import { cn } from "@/shared/lib/utils"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/shared/components/ui/collapsible"; import { Sidebar, SidebarContent, @@ -20,7 +25,6 @@ import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, - SidebarRail, SidebarSeparator, } from "@/shared/components/ui/sidebar"; @@ -28,14 +32,6 @@ const systemNavigation = draftletNavigation.filter((item) => ["/connectors", "/settings"].includes(item.path), ); -function TelegramIcon({ className }: { className?: string }) { - return ( - - ); -} - function isMessageConversation(conversation: Conversation) { return conversation.connector === "telegram" || conversation.threadKind === "chat"; } @@ -56,10 +52,19 @@ function getEmailSender(conversation: Conversation) { ); } -function getConversationIcon(conversation: Conversation, kind: ConversationGroupProps["kind"]) { - if (conversation.connector === "telegram") return TelegramIcon; - if (conversation.connector === "gmail" || kind === "email") return Mail; - return MessageCircle; +function connectorForGroup(kind: ConversationGroupProps["kind"]): Conversation["connector"] { + return kind === "email" ? "gmail" : "telegram"; +} + +function connectorForConversation( + conversation: Conversation, + kind: ConversationGroupProps["kind"], +): Conversation["connector"] { + if (conversation.connector === "gmail" || conversation.connector === "telegram") { + return conversation.connector; + } + + return connectorForGroup(kind); } interface StatusRowProps { @@ -73,7 +78,7 @@ function StatusRow({ label, value, tone }: StatusRowProps) {
-
+
{label}: {value}
@@ -89,70 +94,95 @@ interface ConversationGroupProps { } function ConversationGroup({ label, conversations, activePath, kind }: ConversationGroupProps) { - const Icon = kind === "email" ? Mail : MessageCircle; + const emptyConnector = connectorForGroup(kind); const emptyLabel = kind === "email" ? "No Gmail threads yet" : "No Telegram chats yet"; return ( - - - {label} - - - - {conversations.length > 0 ? ( - conversations.map((conversation) => { - const href = - kind === "email" ? `/email/${conversation.id}` : `/messages/${conversation.id}`; - const isActive = activePath === href; - const title = - kind === "email" ? getEmailSender(conversation) : getConversationName(conversation); - const subtitle = kind === "email" ? conversation.title : undefined; - const preview = conversation.latestMessage; - const RowIcon = getConversationIcon(conversation, kind); + + + + + + {label} + + {conversations.length} + + + + + + + {conversations.length > 0 ? ( + conversations.map((conversation) => { + const href = + kind === "email" ? `/email/${conversation.id}` : `/messages/${conversation.id}`; + const isActive = activePath === href; + const title = + kind === "email" + ? getEmailSender(conversation) + : getConversationName(conversation); + const subtitle = kind === "email" ? conversation.title : undefined; + const preview = conversation.latestMessage; + const rowConnector = connectorForConversation(conversation, kind); - return ( - - - - - - {title} - {subtitle ? ( - - {subtitle} - - ) : null} - {preview ? ( - - {preview} + return ( + + + + {rowConnector === "gmail" ? ( + + ) : ( + + )} + + {title} + {subtitle ? ( + + {subtitle} + + ) : null} + {preview ? ( + + {preview} + + ) : null} - ) : null} - - - + + + + ); + }) + ) : ( + +
+ {emptyConnector === "gmail" ? ( + + ) : ( + + )} + + {emptyLabel} + +
- ); - }) - ) : ( - -
- - {emptyLabel} -
-
- )} -
-
-
+ )} +
+
+ +
+ ); } @@ -165,7 +195,7 @@ function SystemGroup({ activePath }: SystemGroupProps) { return ( - + System @@ -181,9 +211,9 @@ function SystemGroup({ activePath }: SystemGroupProps) { isActive={isActive} tooltip={item.title} className={cn( - "relative h-8 rounded-md text-sidebar-foreground/70 hover:bg-sidebar-accent/70 hover:text-sidebar-accent-foreground", + "text-sidebar-foreground/70 hover:bg-sidebar-accent/70 hover:text-sidebar-accent-foreground relative h-8 rounded-md", isActive && - "bg-sidebar-accent font-medium text-sidebar-accent-foreground before:absolute before:left-1 before:top-1/2 before:h-4 before:w-0.5 before:-translate-y-1/2 before:rounded-full before:bg-primary", + "bg-sidebar-accent text-sidebar-accent-foreground before:bg-primary font-medium before:absolute before:top-1/2 before:left-1 before:h-4 before:w-0.5 before:-translate-y-1/2 before:rounded-full", )} > @@ -211,18 +241,18 @@ export function AppSidebar({ activePath, ...props }: AppSidebarProps) { const emailConversations = conversations.filter(isEmailConversation); return ( - + -
+
Draftlet - + Local drafting companion
@@ -251,7 +281,7 @@ export function AppSidebar({ activePath, ...props }: AppSidebarProps) { -
+
Local status
@@ -267,7 +297,6 @@ export function AppSidebar({ activePath, ...props }: AppSidebarProps) { />
- ); } diff --git a/desktop/src/components/connector-badge.tsx b/desktop/src/components/connector-badge.tsx index 04fdafb..492264d 100644 --- a/desktop/src/components/connector-badge.tsx +++ b/desktop/src/components/connector-badge.tsx @@ -5,10 +5,6 @@ import type { Connector, OllamaProviderStatus, RuntimeStatus } from "@/lib/contr type BadgeStatus = RuntimeStatus | OllamaProviderStatus | "connected"; -function iconFor(connector: Connector) { - return connector === "gmail" ? Mail : MessageCircle; -} - function toneFor(status: BadgeStatus): StatusTone { if (status === "ready" || status === "connected") { return "ready"; @@ -27,16 +23,15 @@ interface ConnectorBadgeProps { label?: string; } -export function ConnectorBadge({ - connector, - status = "offline", - label, -}: ConnectorBadgeProps) { - const Icon = iconFor(connector); +export function ConnectorBadge({ connector, status = "offline", label }: ConnectorBadgeProps) { const resolvedLabel = label ?? (connector === "gmail" ? "Gmail" : "Telegram"); return ( - ); diff --git a/desktop/src/components/empty-state.tsx b/desktop/src/components/empty-state.tsx index 57e9743..354cae5 100644 --- a/desktop/src/components/empty-state.tsx +++ b/desktop/src/components/empty-state.tsx @@ -1,6 +1,5 @@ import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from "@/shared/components/ui/empty"; - interface EmptyStateProps { title: string; description: string; diff --git a/desktop/src/components/error-boundary.tsx b/desktop/src/components/error-boundary.tsx index 639ee4c..5e6ae0b 100644 --- a/desktop/src/components/error-boundary.tsx +++ b/desktop/src/components/error-boundary.tsx @@ -6,17 +6,13 @@ interface DefaultErrorFallbackProps { onReset: () => void; } -export function DefaultErrorFallback({ - error, - onReset, -}: DefaultErrorFallbackProps) { +export function DefaultErrorFallback({ error, onReset }: DefaultErrorFallbackProps) { return ( -
-
+
+

Something went wrong

-

- {error.message || - "An unexpected error occurred while rendering this view."} +

+ {error.message || "An unexpected error occurred while rendering this view."}

-
-
+
- -
- -
-

Chrome extension MVP

-

- Load `extension/` unpacked in Chrome, reload Gmail, select message text, then click the - Draftlet extension. OAuth, Gmail API sync, and sending are intentionally deferred. -

-
@@ -87,8 +70,8 @@ export function GmailStatusCard({ function Metric({ label, value }: { label: string; value: number | string }) { return ( -
-

{label}

+
+

{label}

{value}

); diff --git a/desktop/src/modules/connectors/components/telegram-connect-modal.tsx b/desktop/src/modules/connectors/components/telegram-connect-modal.tsx index e5dea25..958d263 100644 --- a/desktop/src/modules/connectors/components/telegram-connect-modal.tsx +++ b/desktop/src/modules/connectors/components/telegram-connect-modal.tsx @@ -115,9 +115,9 @@ export function TelegramConnectModal({ open, onOpenChange }: TelegramConnectModa {connected ? ( -
+

Connected{username ? ` as ${username}` : ""}

-

+

Draftlet can now ingest new incoming Telegram messages into the local runtime.

@@ -248,9 +248,9 @@ export function TelegramConnectModal({ open, onOpenChange }: TelegramConnectModa -
+

Scan from Telegram

-

+

Open Telegram on your phone, go to Settings {"->"} Devices {"->"} Link Desktop Device, then scan this code.

@@ -260,11 +260,11 @@ export function TelegramConnectModal({ open, onOpenChange }: TelegramConnectModa {qrUrl && qrState !== "expired" ? ( ) : ( -
+
No active QR
)} -

+

{qrState === "expired" ? "This QR code expired. Generate a new one." : qrUrl @@ -272,7 +272,7 @@ export function TelegramConnectModal({ open, onOpenChange }: TelegramConnectModa : "Generate a QR code to start."}

{qrStatus.data?.error ? ( -

{qrStatus.data.error}

+

{qrStatus.data.error}

) : null}
@@ -319,20 +319,20 @@ export interface AuthHintProps { function AuthHint({ error, delivery, nextDelivery, length, timeout }: AuthHintProps) { if (error) { return ( -

+

{error}

); } if (!delivery) { return ( -

+

Send a code first. Telegram usually delivers it to the official Telegram chat in your app.

); } return ( -

+

Telegram chose {delivery}. Check the official Telegram chat in your app. {nextDelivery ? ` Alternate delivery: ${nextDelivery}.` : ""} {length ? ` Code length: ${length} digits.` : ""} diff --git a/desktop/src/modules/conversation-detail/components/chat-message-bubble.tsx b/desktop/src/modules/conversation-detail/components/chat-message-bubble.tsx index d8b915d..fcbada3 100644 --- a/desktop/src/modules/conversation-detail/components/chat-message-bubble.tsx +++ b/desktop/src/modules/conversation-detail/components/chat-message-bubble.tsx @@ -37,7 +37,7 @@ export function ChatMessageBubble({ )} > {incoming && !compact ? ( - + {getInitials(message.author)} ) : null} @@ -51,9 +51,9 @@ export function ChatMessageBubble({

{incoming && !compact ? ( -

{message.author}

+

{message.author}

) : null} -

{message.body}

+

{message.body}

diff --git a/desktop/src/modules/conversation-detail/components/conversation-ai-panel.tsx b/desktop/src/modules/conversation-detail/components/conversation-ai-panel.tsx index 103960c..c968940 100644 --- a/desktop/src/modules/conversation-detail/components/conversation-ai-panel.tsx +++ b/desktop/src/modules/conversation-detail/components/conversation-ai-panel.tsx @@ -18,38 +18,38 @@ export function ConversationAiPanel({ conversation, latestDraft }: ConversationA const selectedMessages = latestDraft?.selectedMessages ?? []; return ( -

+
-
+

Draftlet memory

-

Local AI drafting context

+

Local AI drafting context

-
+
- + {getDraftStateLabel(latestDraft)}
-

+

Drafts are generated and edited inline. Insert locally when you want a Draftlet-only timeline entry, or explicitly confirm a supported external send.

-

+

Reply target

{replyTarget ? ( -
+

{replyTarget.author}

-

+

{replyTarget.body}

@@ -57,7 +57,7 @@ export function ConversationAiPanel({ conversation, latestDraft }: ConversationA
) : ( -

+

No draft reply target yet. Draftlet will target the latest incoming message when you draft a reply.

@@ -65,23 +65,23 @@ export function ConversationAiPanel({ conversation, latestDraft }: ConversationA
-

+

Context snapshot

{selectedMessages.length > 0 ? (
{selectedMessages.slice(0, 3).map((message, index) => ( -
+

{message.author}

-

+

{message.detail}

))}
) : ( -

+

No saved draft context. The next draft will use recent messages from this thread.

)} diff --git a/desktop/src/modules/conversation-detail/components/conversation-draft-dock.tsx b/desktop/src/modules/conversation-detail/components/conversation-draft-dock.tsx deleted file mode 100644 index 312bba6..0000000 --- a/desktop/src/modules/conversation-detail/components/conversation-draft-dock.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { Bot, ShieldCheck } from "lucide-react"; - -import type { Conversation, Draft } from "@/lib/contracts"; -import { getDraftReplyTarget, getDraftStateLabel } from "@/modules/conversation-detail/utils"; -import { Button } from "@/shared/components/ui/button"; - -export interface ConversationDraftDockProps { - conversation: Conversation; - latestDraft?: Draft; - isGenerating: boolean; - onGenerate: () => void; -} - -export function ConversationDraftDock({ - conversation, - latestDraft, - isGenerating, - onGenerate, -}: ConversationDraftDockProps) { - const replyTarget = getDraftReplyTarget(latestDraft, conversation); - - return ( -
-
-
-
- - {getDraftStateLabel(latestDraft)} -
-

- {replyTarget - ? `Replying to ${replyTarget.author}: ${replyTarget.body}` - : "Generate a local draft from this thread and edit it inline."} -

-

- - Insert locally does not send externally. Supported external sends require confirmation. -

-
-
- {!latestDraft ? ( - - ) : null} -
-
-
- ); -} diff --git a/desktop/src/modules/conversation-detail/components/conversation-header.tsx b/desktop/src/modules/conversation-detail/components/conversation-header.tsx index 4e950a7..71ff837 100644 --- a/desktop/src/modules/conversation-detail/components/conversation-header.tsx +++ b/desktop/src/modules/conversation-detail/components/conversation-header.tsx @@ -23,6 +23,18 @@ export interface ConversationHeaderProps { className?: string; } +function normalizeLabel(value?: string | null) { + return value?.trim().toLocaleLowerCase() ?? ""; +} + +function getSecondaryLabel(conversation: Conversation) { + const primary = normalizeLabel( + conversation.title || conversation.contact || conversation.participants, + ); + const candidates = [conversation.participants, conversation.contact]; + return candidates.find((candidate) => candidate && normalizeLabel(candidate) !== primary); +} + export function ConversationHeader({ conversation, latestDraft, @@ -38,6 +50,7 @@ export function ConversationHeader({ const threadKind = getThreadViewKind(conversation); const threadLabel = threadKind === "chat" ? "Chat thread" : threadKind === "email" ? "Email thread" : "Timeline"; + const secondaryLabel = getSecondaryLabel(conversation); const isRail = variant === "rail"; const isCompact = variant === "compact"; @@ -45,9 +58,9 @@ export function ConversationHeader({ return (
@@ -73,12 +86,12 @@ export function ConversationHeader({

- {conversation.participants || conversation.contact} - · + {secondaryLabel ? {secondaryLabel} : null} + {secondaryLabel ? · : null} {conversation.messages.length} messages · Captured {formatDateTime(conversation.capturedAt)} @@ -89,13 +102,13 @@ export function ConversationHeader({

Latest: {conversation.latestMessage}

-
+
Local capture {hasDraft ? ( {getDraftStateLabel(latestDraft)} diff --git a/desktop/src/modules/conversation-detail/components/conversation-workspace-layout.tsx b/desktop/src/modules/conversation-detail/components/conversation-workspace-layout.tsx index 2544b89..0161663 100644 --- a/desktop/src/modules/conversation-detail/components/conversation-workspace-layout.tsx +++ b/desktop/src/modules/conversation-detail/components/conversation-workspace-layout.tsx @@ -14,7 +14,7 @@ export function ConversationWorkspaceLayout({ return (
-
{thread}
+
{thread}
{dock}
diff --git a/desktop/src/modules/conversation-detail/components/day-divider.tsx b/desktop/src/modules/conversation-detail/components/day-divider.tsx index 87ab055..a992779 100644 --- a/desktop/src/modules/conversation-detail/components/day-divider.tsx +++ b/desktop/src/modules/conversation-detail/components/day-divider.tsx @@ -1,11 +1,11 @@ export function DayDivider({ label }: { label: string }) { return (
-
- +
+ {label} -
+
); } diff --git a/desktop/src/modules/conversation-detail/components/email-message-card.tsx b/desktop/src/modules/conversation-detail/components/email-message-card.tsx index 5088ec0..d00aa45 100644 --- a/desktop/src/modules/conversation-detail/components/email-message-card.tsx +++ b/desktop/src/modules/conversation-detail/components/email-message-card.tsx @@ -21,16 +21,16 @@ export function EmailMessageCard({ message, replyTarget }: EmailMessageCardProps const hasQuote = quotedBody.length > 0; return ( -
+
- +

{message.author}

-

{getMessageLabel(message)}

+

{getMessageLabel(message)}

-
@@ -39,7 +39,7 @@ export function EmailMessageCard({ message, replyTarget }: EmailMessageCardProps target={replyTarget} unresolvedExternalId={!replyTarget ? message.replyToExternalMessageId : undefined} /> -

+

{visibleBody || message.body}

{hasQuote ? ( @@ -54,7 +54,7 @@ export function EmailMessageCard({ message, replyTarget }: EmailMessageCardProps {showQuote ? "Hide quoted text" : "Show quoted text"} {showQuote ? ( -
+              
                 {quotedBody}
               
) : null} diff --git a/desktop/src/modules/conversation-detail/components/message-timeline.tsx b/desktop/src/modules/conversation-detail/components/message-timeline.tsx index 59f058d..2be7d1f 100644 --- a/desktop/src/modules/conversation-detail/components/message-timeline.tsx +++ b/desktop/src/modules/conversation-detail/components/message-timeline.tsx @@ -36,7 +36,7 @@ export interface MessageTimelineProps { export function MessageTimeline({ conversation, messages }: MessageTimelineProps) { if (messages.length === 0) { return ( -
+
No captured messages yet for this conversation.
); @@ -59,16 +59,16 @@ export function MessageTimeline({ conversation, messages }: MessageTimelineProps

{message.author}

- + {formatDateTime(message.timestamp)}
-

+

{meta.label}

-

{message.body}

+

{message.body}

{message.status ? ( -

{message.status}

+

{message.status}

) : null}
); diff --git a/desktop/src/modules/conversation-detail/components/reply-quote.tsx b/desktop/src/modules/conversation-detail/components/reply-quote.tsx index b85249d..94c42c7 100644 --- a/desktop/src/modules/conversation-detail/components/reply-quote.tsx +++ b/desktop/src/modules/conversation-detail/components/reply-quote.tsx @@ -13,14 +13,14 @@ export function ReplyQuote({ target, unresolvedExternalId, compact = false }: Re return (
-

+

Replying to {target?.author ?? "external message"}

-

+

{target?.body ?? unresolvedExternalId}

diff --git a/desktop/src/modules/conversation-detail/hooks/use-mark-conversation-captured.ts b/desktop/src/modules/conversation-detail/hooks/use-mark-conversation-captured.ts index 989c24d..91cc4fb 100644 --- a/desktop/src/modules/conversation-detail/hooks/use-mark-conversation-captured.ts +++ b/desktop/src/modules/conversation-detail/hooks/use-mark-conversation-captured.ts @@ -1,4 +1,4 @@ -import { useEffect } from "react"; +import { useEffect, useRef } from "react"; import { useMarkConversationCaptured as useMarkConversationCapturedMutation } from "@/lib/queries/conversations"; @@ -6,13 +6,22 @@ export function useMarkConversationCaptured( conversationId: string | undefined, recentlyCaptured: boolean | undefined, ) { - const markConversationCaptured = useMarkConversationCapturedMutation(); + const { mutate } = useMarkConversationCapturedMutation(); + const markedConversationIdsRef = useRef(new Set()); useEffect(() => { - if (!conversationId || !recentlyCaptured) { + if (!conversationId) { return; } - markConversationCaptured.mutate(conversationId); - }, [conversationId, markConversationCaptured, recentlyCaptured]); + if (!recentlyCaptured) { + markedConversationIdsRef.current.delete(conversationId); + return; + } + + if (markedConversationIdsRef.current.has(conversationId)) return; + + markedConversationIdsRef.current.add(conversationId); + mutate(conversationId); + }, [conversationId, mutate, recentlyCaptured]); } diff --git a/desktop/src/modules/draft-workspace/components/alternatives-panel.tsx b/desktop/src/modules/draft-workspace/components/alternatives-panel.tsx index b92fb75..6d45b08 100644 --- a/desktop/src/modules/draft-workspace/components/alternatives-panel.tsx +++ b/desktop/src/modules/draft-workspace/components/alternatives-panel.tsx @@ -21,9 +21,9 @@ export function AlternativesPanel({ onGenerateVariant, }: AlternativesPanelProps) { return ( -