diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 7e90477..8f1a00b 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -15,6 +15,7 @@
- Release automation is handled by `release-please`; keep `release-please-config.json` and `.release-please-manifest.json` aligned with the latest published tag, keep root tags in the existing plain `v*` format, and prefer Conventional Commit subjects (`fix:`, `feat:`, `deps:`) so release PRs are generated correctly.
- Merged release PRs are finalized by `.github/workflows/release.yml`: the workflow detects `chore(main): release ink x.y.z` merges, creates the canonical `v*` tag plus a compatibility `ink-v*` tag, publishes the GitHub release from `v*`, and clears stale `autorelease:*` labels so the next release PR is not blocked.
- Protected-branch repos should prefer a `RELEASE_PLEASE_TOKEN` PAT for the release workflow so bot-created release PRs trigger the normal PR checks.
+- In this repo, `main` is protected and all changes land through PRs, so `.github/workflows/pr-checks.yml` is the validation gate, `.github/workflows/codeql.yml` runs on PRs plus schedule, and `.github/workflows/release.yml` should only assemble `ink-app.html` and publish the release instead of rerunning the full test suite after merge.
- Contributor-facing workflow and validation steps now live in `CONTRIBUTING.md`; keep build, test, and single-file output guidance aligned with that document when project workflows change.
- Declarative WebMCP note creation lives in the `#webmcpNoteForm` form in `ink.template.html`; keep its submit path wired to `createNoteFromTool()` in `src/app/workspace-io.ts` so agent-invoked note creation stays in the single-page app and does not navigate away.
- Cogito Mode work is tracked in `openspec/changes/add-cogito-mode/`; preserve the strict three-question JSON contract and the markdown insertion block format (`> ### AI` then question text) when implementing it.
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 69efcdb..9a7b485 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -12,8 +12,6 @@
name: "CodeQL Advanced"
on:
- push:
- branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 00739f4..876bb08 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -85,13 +85,9 @@ jobs:
if: ${{ steps.merged_release.outputs.is_release_merge == 'true' }}
run: npm ci
- - name: Build
+ - name: Assemble release artifact
if: ${{ steps.merged_release.outputs.is_release_merge == 'true' }}
- run: npm run build
-
- - name: Run tests
- if: ${{ steps.merged_release.outputs.is_release_merge == 'true' }}
- run: npm test
+ run: node build/compile-and-assemble.js
- name: Create release tags
if: ${{ steps.merged_release.outputs.is_release_merge == 'true' }}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4a07dab..1d80d8a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -69,6 +69,7 @@ Feature ideas are welcome. Open an issue describing the problem you want to solv
- When a release PR is merged, the release workflow finalizes it by creating the plain `v*` tag, adding a compatibility `ink-v*` tag for release-please state reconciliation, publishing the GitHub release from `v*`, and clearing stale autorelease labels.
- Merge the release PR through the normal protected-branch flow to create the Git tag and publish the GitHub release.
- If you want the release PR to trigger the normal PR checks automatically, configure a `RELEASE_PLEASE_TOKEN` secret with a PAT that can open pull requests in this repository.
+- CI validation should happen in PR workflows before merge; the release workflow is intentionally limited to regenerating `ink-app.html` for the tagged release artifact instead of rerunning the full build/test suite after the protected-branch merge.
## Style Guide
diff --git a/repomix-output.xml b/repomix-output.xml
index b8b34e6..c797739 100644
--- a/repomix-output.xml
+++ b/repomix-output.xml
@@ -27413,6 +27413,62 @@ ${t.trim()}
}
+
+
+
+
+
+
+
+
+
+export {};
+
+
{
"compilerOptions": {
@@ -27839,6 +27899,240 @@ Users want to authenticate with their GitHub account to save their preferences a
- [ ] 3.2 Create callback page for production deployment
+
+## ADDED Requirements
+### Requirement: Canonical Project Logo Asset
+The repository SHALL store a canonical project logo source file that is used for documentation and favicon generation.
+
+#### Scenario: Canonical logo source is available
+- **WHEN** a maintainer inspects branding files in the repository
+- **THEN** a single canonical logo source exists in a stable project path
+- **AND** the source is suitable for deriving README and favicon assets
+
+### Requirement: README Logo Rendering
+The project documentation SHALL render the project logo in `README.md` using repository-relative asset references.
+
+#### Scenario: README displays logo
+- **WHEN** `README.md` is rendered by a GitHub-compatible markdown renderer
+- **THEN** the logo is visible near the document header
+- **AND** the logo reference resolves without external network dependencies
+
+### Requirement: Favicon Assets Derived from Logo
+The project SHALL provide favicon assets derived from the canonical logo source.
+
+#### Scenario: Favicon assets are generated and available
+- **WHEN** the favicon generation workflow is run
+- **THEN** favicon files are produced in the documented output location
+- **AND** generated filenames match those referenced by the application template
+
+### Requirement: Application Template Favicon References
+The application HTML template SHALL reference project favicon assets so browser tabs display the project icon.
+
+#### Scenario: Browser tab uses project favicon
+- **WHEN** a user opens the built application in a modern browser
+- **THEN** the browser resolves favicon references from the application output
+- **AND** the tab icon reflects the project logo branding
+
+
+
+# Change: Add Project Logo to README and Favicon Assets
+
+## Why
+The repository currently lacks consistent branding assets in project documentation and browser metadata. Adding the provided logo to `README.md` and defining favicon outputs improves recognizability and presentation.
+
+## What Changes
+- Add a canonical logo asset in the repository based on `~/Downloads/ink_logo.svg`.
+- Update `README.md` to render the project logo near the top of the document.
+- Define and implement favicon outputs derived from the same logo source.
+- Wire favicon references into the app HTML template so browser tabs use project branding.
+- Document the asset location and favicon generation/update workflow.
+
+## Impact
+- Affected specs: `branding-assets`
+- Affected code:
+ - `README.md`
+ - `ink.template.html`
+ - `build/` scripts (if favicon generation is automated in build)
+ - `dist/` favicon artifacts
+ - `assets/` branding source files (new)
+
+
+
+## 1. Asset Setup
+- [x] 1.1 Add the provided logo SVG to a canonical repository path for shared branding usage.
+- [x] 1.2 Define favicon output formats and filenames derived from the canonical logo source.
+
+## 2. Documentation and Template Integration
+- [x] 2.1 Update `README.md` to display the project logo with stable relative linking.
+- [x] 2.2 Update the HTML template to include favicon references that resolve in the built output.
+
+## 3. Build and Distribution
+- [x] 3.1 Implement or document the process to generate favicon artifacts from the SVG source.
+- [x] 3.2 Ensure generated favicon assets are available in expected output locations.
+
+## 4. Validation
+- [x] 4.1 Verify `README.md` renders the logo correctly on GitHub-compatible markdown viewers.
+- [x] 4.2 Verify the built app/tab displays the new favicon in a modern browser.
+- [x] 4.3 Verify documentation describes how to refresh logo-derived assets when the SVG changes.
+
+
+
+## ADDED Requirements
+### Requirement: Prioritized Maintainability Refactor Plan
+The project SHALL define a maintainability refactor plan with explicit High, Medium, and Low priority tiers based on engineering risk and value.
+
+#### Scenario: Plan is prioritized and actionable
+- **WHEN** a maintainer reviews the approved change proposal
+- **THEN** the proposal includes clearly separated High, Medium, and Low priority work
+- **AND** each tier contains concrete codebase targets
+
+### Requirement: Evidence-Based Refactor Scope
+Refactor recommendations MUST be grounded in current repository evidence and MUST avoid unnecessary changes.
+
+#### Scenario: Recommendations are justified
+- **WHEN** a recommendation proposes delete, rename, or restructure actions
+- **THEN** it references observable repository state (for example dead code, missing script targets, or duplicated logic)
+- **AND** the proposal explicitly identifies areas that should remain unchanged when already aligned
+
+### Requirement: Behavior-Preserving Refactor Guardrails
+Maintainability refactors SHALL preserve existing user-visible behavior unless a separate feature change is approved.
+
+#### Scenario: Refactor keeps current behavior stable
+- **WHEN** maintainability refactor tasks are implemented
+- **THEN** build and test workflows continue to pass
+- **AND** core authoring flow behavior (open workspace, create note, edit, save) remains unchanged
+
+### Requirement: Tooling and Documentation Consistency
+Documented commands and scripts MUST map to files and behavior that exist in the repository.
+
+#### Scenario: Script/docs mismatch is resolved
+- **WHEN** package scripts or README commands are documented
+- **THEN** each command resolves to existing code paths
+- **AND** stale or broken command references are removed or restored
+
+
+
+## Context
+Ink is intentionally lightweight and single-file at runtime. The refactor must preserve behavior while making source code easier for LLMs to read, regenerate, and safely modify.
+
+## Goals / Non-Goals
+- Goals:
+ - Reduce cognitive load in `src/app.ts` by splitting by feature boundaries.
+ - Remove stale/dead paths that create false complexity.
+ - Improve naming so build and runtime responsibilities are explicit.
+ - Keep behavior and user workflow unchanged.
+- Non-Goals:
+ - Framework migration.
+ - UX redesign.
+ - New product features.
+
+## Decisions
+- Decision: Refactor in phases (High -> Medium -> Low) with behavior-preserving checkpoints.
+ - Rationale: minimizes regression risk and keeps scope manageable.
+- Decision: Keep a thin composition entrypoint and move concerns into small modules.
+ - Rationale: supports predictable regeneration and targeted testing.
+- Decision: Do not force changes where current code is already clear (for example `src/tags.ts`).
+ - Rationale: avoids churn and preserves momentum.
+
+## Risks / Trade-offs
+- Risk: File splits can break implicit state assumptions.
+ - Mitigation: define explicit typed state contracts and test around open/save/refresh flows.
+- Risk: Renaming scripts can disrupt local habits.
+ - Mitigation: keep temporary aliases and document migration in README.
+
+## Migration Plan
+1. Remove dead code and duplicate timers in `src/app.ts`.
+2. Introduce module boundaries for workspace scan, editor operations, and rendering.
+3. Rename build scripts/files with compatibility aliases.
+4. Re-run build + QUnit + Cypress and update docs.
+
+## Open Questions
+- Should `sync:from-ink-app` be restored (by adding script) or removed from docs/scripts?
+- Should `src/storage.ts` remain a test fixture in `src/` or move to a dedicated test-support path?
+
+
+
+# Change: LLM-Aligned Maintainability Refactor Plan
+
+## Why
+The current codebase works, but it has several maintainability risks that reduce predictability and regenerability for LLM-driven development. The biggest issue is concentration of behavior in one large file, plus stale scripts and dead code paths that make intent harder to reason about.
+
+## What Changes
+- Create a phased refactor plan aligned to project coding principles: clarity, pragmatism, rigor.
+- Prioritize only high-value changes and explicitly avoid churn where behavior is already clear and stable.
+- Define concrete rename/restructure/delete targets backed by repository evidence.
+- Add acceptance criteria so refactor work can be verified without changing product behavior.
+
+### High Priority
+- Split `src/app.ts` into small feature modules with a thin entrypoint (`main` + feature-oriented files).
+- Remove duplicated auto-refresh behavior (currently both `startAutoRefresh()` timer and a separate global `setInterval` run every 10s).
+- Fix stale/broken tooling contract: `sync:from-ink-app` references `build/sync-from-ink-app.js`, but the file is missing.
+- Remove dead fields/utilities in `src/app.ts`:
+ - `searchScanDebounceTimer`
+ - `lastContentSearchToken`
+ - `sleep()`
+ - `humanDate()`
+ - `debounce()`
+
+### Medium Priority
+- Rename ambiguous build scripts for intent clarity:
+ - `build/inject.js` -> `build/assemble-single-file.js`
+ - `build/build.js` -> `build/compile-and-assemble.js`
+- Extract File System Access API handling into a dedicated module (permissions, handles, scan traversal) to reduce coupling with UI rendering.
+- Replace broad `any` usage in app state with narrow interfaces for notes, tree nodes, and workspace handles.
+- Remove redundant DOM lookups in `src/app.ts` where elements are already captured once.
+
+### Low Priority
+- Decide whether test-only storage logic should be renamed for intent:
+ - `src/storage.ts` -> `src/test-support/storage-fixture.ts` (if kept test-only)
+- Decide whether generated test bundles should stay committed:
+ - `dist/test/*` can be generated during `npm run build:test` and excluded from source control.
+- Tighten Cypress scaffolding comments/config only if it improves signal-to-noise (avoid cosmetic edits).
+
+### Keep As-Is (No Change Needed)
+- `src/tags.ts` is already small, explicit, and testable.
+- Build pipeline remains lightweight and appropriate for single-file distribution constraints.
+- Existing QUnit and Cypress coverage should be retained and expanded only around touched refactor areas.
+
+## Impact
+- Affected specs: `codebase-maintainability`
+- Affected code:
+ - `src/app.ts`
+ - `src/storage.ts` (rename/rehome decision)
+ - `build/build.js`
+ - `build/inject.js`
+ - `package.json`
+ - `README.md`
+ - `Makefile` (if command wrappers change)
+ - `tests/qunit/*`
+ - `cypress/*`
+
+
+
+## 1. High Priority (Safety + Clarity)
+- [x] 1.1 Remove duplicate auto-refresh scheduling and keep one refresh control path.
+- [x] 1.2 Remove dead fields/utilities from `src/app.ts` (`searchScanDebounceTimer`, `lastContentSearchToken`, `sleep`, `humanDate`, `debounce`).
+- [x] 1.3 Resolve broken `sync:from-ink-app` contract by either restoring `build/sync-from-ink-app.js` or removing stale script/docs.
+- [x] 1.4 Split `src/app.ts` into a small entrypoint plus feature modules with explicit interfaces.
+
+## 2. Medium Priority (Structure + Naming)
+- [x] 2.1 Rename build scripts/files for clearer responsibility and keep compatibility aliases during migration.
+- [x] 2.2 Extract File System Access API logic into a dedicated module.
+- [x] 2.3 Replace broad `any` usage in app state/types with narrower interfaces.
+- [x] 2.4 Remove redundant element re-queries in app initialization.
+
+## 3. Low Priority (Cleanup)
+- [x] 3.1 Decide whether to rename/rehome `src/storage.ts` as test-support-only code.
+- [x] 3.2 Decide whether `dist/test/*` should be generated-only and excluded from source control. (Decision: keep committed for now to preserve current repository pattern with generated distribution artifacts under `dist/`.)
+- [x] 3.3 Clean minor Cypress scaffolding noise only when it improves maintainability.
+
+## 4. Validation
+- [x] 4.1 `npm run build` succeeds and produces a working `ink-app.html`.
+- [x] 4.2 `npm run test:qunit` passes.
+- [x] 4.3 `npm run test:cypress` passes.
+- [x] 4.4 README and script references match actual files/commands.
+
+
# OpenSpec Instructions
@@ -29090,39 +29384,154 @@ export function createUserManager() {
const data: GitHubApiUserResponse = await response.json();
- cachedUser = {
- login: data.login,
- id: data.id,
- avatarUrl: data.avatar_url,
- name: data.name,
- };
+ cachedUser = {
+ login: data.login,
+ id: data.id,
+ avatarUrl: data.avatar_url,
+ name: data.name,
+ };
+
+ return cachedUser;
+ }
+
+ /**
+ * Clears cached user data
+ * Called on logout
+ */
+ function clearCache(): void {
+ cachedUser = null;
+ }
+
+ /**
+ * Gets cached user without fetching
+ */
+ function getCachedUser(): GitHubUser | null {
+ return cachedUser;
+ }
+
+ return {
+ fetchUser,
+ clearCache,
+ getCachedUser,
+ };
+}
+
+export type GitHubUserManager = ReturnType;
+
+
+
+export interface KeyValueStorage {
+ readonly length: number;
+ key(index: number): string | null;
+ getItem(key: string): string | null;
+ setItem(key: string, value: string): void;
+ removeItem(key: string): void;
+}
+
+const STORAGE_PREFIX = "ink.workspace.";
+
+function workspaceKey(name: string): string {
+ return `${STORAGE_PREFIX}${name}`;
+}
+
+function normalizeWorkspaceName(name: string): string {
+ return name.trim();
+}
+
+function parseWorkspace(value: string | null): Record {
+ if (!value) {
+ return {};
+ }
+
+ try {
+ const parsed = JSON.parse(value) as unknown;
+ if (!parsed || typeof parsed !== "object") {
+ return {};
+ }
+
+ const files: Record = {};
+ for (const [key, content] of Object.entries(parsed as Record)) {
+ if (typeof key === "string" && typeof content === "string") {
+ files[key] = content;
+ }
+ }
+
+ return files;
+ } catch {
+ return {};
+ }
+}
+
+function writeWorkspace(storage: KeyValueStorage, workspace: string, files: Record): void {
+ storage.setItem(workspaceKey(workspace), JSON.stringify(files));
+}
+
+export function listWorkspaces(storage: KeyValueStorage): string[] {
+ const workspaces: string[] = [];
+
+ for (let index = 0; index < storage.length; index += 1) {
+ const key = storage.key(index);
+ if (!key || !key.startsWith(STORAGE_PREFIX)) {
+ continue;
+ }
+
+ workspaces.push(key.slice(STORAGE_PREFIX.length));
+ }
+
+ return workspaces.sort((a, b) => a.localeCompare(b));
+}
+
+export function ensureWorkspace(storage: KeyValueStorage, workspaceName: string): string {
+ const normalizedName = normalizeWorkspaceName(workspaceName);
+ if (!normalizedName) {
+ throw new Error("Workspace name cannot be empty.");
+ }
+
+ const key = workspaceKey(normalizedName);
+ if (storage.getItem(key) === null) {
+ writeWorkspace(storage, normalizedName, {});
+ }
+
+ return normalizedName;
+}
+
+export function listFiles(storage: KeyValueStorage, workspaceName: string): string[] {
+ const files = parseWorkspace(storage.getItem(workspaceKey(workspaceName)));
+ return Object.keys(files).sort((a, b) => a.localeCompare(b));
+}
- return cachedUser;
+export function createFile(storage: KeyValueStorage, workspaceName: string, fileName: string): string {
+ const normalizedFileName = fileName.trim();
+ if (!normalizedFileName) {
+ throw new Error("File name cannot be empty.");
}
- /**
- * Clears cached user data
- * Called on logout
- */
- function clearCache(): void {
- cachedUser = null;
+ const workspace = ensureWorkspace(storage, workspaceName);
+ const files = parseWorkspace(storage.getItem(workspaceKey(workspace)));
+ if (!(normalizedFileName in files)) {
+ files[normalizedFileName] = "";
+ writeWorkspace(storage, workspace, files);
}
- /**
- * Gets cached user without fetching
- */
- function getCachedUser(): GitHubUser | null {
- return cachedUser;
- }
+ return normalizedFileName;
+}
- return {
- fetchUser,
- clearCache,
- getCachedUser,
- };
+export function readFile(storage: KeyValueStorage, workspaceName: string, fileName: string): string {
+ const files = parseWorkspace(storage.getItem(workspaceKey(workspaceName)));
+ return files[fileName] ?? "";
}
-export type GitHubUserManager = ReturnType;
+export function saveFile(
+ storage: KeyValueStorage,
+ workspaceName: string,
+ fileName: string,
+ content: string,
+): void {
+ const workspace = ensureWorkspace(storage, workspaceName);
+ const files = parseWorkspace(storage.getItem(workspaceKey(workspace)));
+ files[fileName] = content;
+ writeWorkspace(storage, workspace, files);
+}
@@ -29610,62 +30019,6 @@ npx repomix@latest
git add repomix-output.xml
-
-
-
-
-
-
-
-
function createFakeFileHandle(name, initialContent = "") {
let content = initialContent;
@@ -29793,179 +30146,47 @@ describe("cogito mode", () => {
};
},
},
- },
- };
- },
- };
- },
- });
- });
-
- it("opens the panel, switches model, generates questions, and inserts one into the editor", () => {
- cy.get("[data-action=\"open-workspace\"]").click({ force: true });
- cy.get("[data-action=\"new-note\"]").click({ force: true });
- cy.get("#editor").clear().type(markdown);
-
- cy.get("#cogitoToggleBtn")
- .should("have.attr", "aria-expanded", "false")
- .click()
- .should("have.attr", "aria-expanded", "true");
- cy.get("#cogitoPanel").should("be.visible");
- cy.get(".split").should("have.class", "with-cogito");
-
- cy.get("#cogitoDeepBtn").click().should("have.class", "active");
- cy.get("#cogitoLiteBtn").should("not.have.class", "active");
-
- cy.get("#cogitoGenerateBtn").click();
-
- cy.get("#statusBadge").should("contain", "Cogito questions ready");
- cy.get("#cogitoStatus").should("contain", "Questions ready");
- cy.get("#cogitoQuestionList .cogitoQuestionItem").should("have.length", 3);
- cy.get("#cogitoQuestionList .cogitoQuestionText")
- .eq(0)
- .should("contain", "What problem does local-first editing solve here?");
-
- cy.window().then((win) => {
- expect(win.__cogitoCreateEngineCalls).to.deep.equal(["Qwen3-8B-q4f16_1-MLC"]);
- expect(win.__cogitoCompletions).to.have.length(1);
- expect(win.__cogitoCompletions[0].messages[1].content).to.contain(markdown);
- });
-
- cy.contains("#cogitoQuestionList .cogitoInsertBtn", "Insert").first().click();
- cy.get("#statusBadge").should("contain", "Inserted AI question");
- cy.get("#editor").should("have.value", `${markdown}> ### AI\nWhat problem does local-first editing solve here?\n`);
- });
-});
-
-
-
-function createFakeFileHandle(name, initialContent = "") {
- let content = initialContent;
- let lastModified = Date.now();
-
- return {
- kind: "file",
- name,
- async queryPermission() {
- return "granted";
- },
- async requestPermission() {
- return "granted";
- },
- async getFile() {
- return new File([content], name, { type: "text/markdown", lastModified });
- },
- async createWritable() {
- return {
- async write(nextContent) {
- content = String(nextContent);
- lastModified = Date.now();
- },
- async close() {},
- };
- },
- __read() {
- return content;
- },
- };
-}
-
-function createFakeDirectoryHandle(name) {
- const entries = new Map();
-
- return {
- kind: "directory",
- name,
- async queryPermission() {
- return "granted";
- },
- async requestPermission() {
- return "granted";
- },
- async *entries() {
- for (const entry of entries.entries()) {
- yield entry;
- }
- },
- async getFileHandle(fileName, options = {}) {
- const existing = entries.get(fileName);
- if (existing) {
- return existing;
- }
- if (!options.create) {
- throw new Error(`File not found: ${fileName}`);
- }
- const handle = createFakeFileHandle(fileName);
- entries.set(fileName, handle);
- return handle;
- },
- async getDirectoryHandle(dirName, options = {}) {
- const existing = entries.get(dirName);
- if (existing) {
- return existing;
- }
- if (!options.create) {
- throw new Error(`Directory not found: ${dirName}`);
- }
- const handle = createFakeDirectoryHandle(dirName);
- entries.set(dirName, handle);
- return handle;
- },
- };
-}
-
-describe("document linter export", () => {
- const workspaceName = "linter-workspace";
- const noteName = "linter-note";
- const noteContent = "# Linter export test\n\nThis sentence is intentionally very long so the readability rule has something to report.";
-
- beforeEach(() => {
- cy.visit("/ink-app.html", {
- onBeforeLoad(win) {
- const root = createFakeDirectoryHandle(workspaceName);
-
- win.prompt = (message) => {
- if (message.includes("New note name")) {
- return noteName;
- }
- return null;
- };
-
- win.confirm = () => true;
- win.FileSystemHandle = function FileSystemHandle() {};
- win.showDirectoryPicker = async () => root;
- win.__fakeWorkspace = root;
- win.__linterExportBlobs = [];
- win.URL.createObjectURL = (blob) => {
- win.__linterExportBlobs.push(blob);
- return "blob:linter-report";
+ },
+ };
+ },
};
- win.URL.revokeObjectURL = () => {};
},
});
});
- it("exports the current linter report as markdown", () => {
+ it("opens the panel, switches model, generates questions, and inserts one into the editor", () => {
cy.get("[data-action=\"open-workspace\"]").click({ force: true });
cy.get("[data-action=\"new-note\"]").click({ force: true });
- cy.get("#editor").clear().type(noteContent);
+ cy.get("#editor").clear().type(markdown);
- cy.get("#documentLinterToggleBtn").click();
- cy.get("#documentLinterAnalyzeBtn").click();
- cy.get("#statusBadge").should("contain", "Analysis complete");
+ cy.get("#cogitoToggleBtn")
+ .should("have.attr", "aria-expanded", "false")
+ .click()
+ .should("have.attr", "aria-expanded", "true");
+ cy.get("#cogitoPanel").should("be.visible");
+ cy.get(".split").should("have.class", "with-cogito");
- cy.get("#documentLinterExportBtn").click();
- cy.get("#statusBadge").should("contain", "Exported report");
+ cy.get("#cogitoDeepBtn").click().should("have.class", "active");
+ cy.get("#cogitoLiteBtn").should("not.have.class", "active");
- cy.window().then(async (win) => {
- expect(win.__linterExportBlobs).to.have.length(1);
- const report = await win.__linterExportBlobs[0].text();
- expect(report).to.contain("# Document Linter Review");
- expect(report).to.contain("## Overall");
- expect(report).to.contain("Overall score:");
- expect(report).to.contain("Readability");
- expect(report).to.contain("Opening is informative, not directive");
+ cy.get("#cogitoGenerateBtn").click();
+
+ cy.get("#statusBadge").should("contain", "Cogito questions ready");
+ cy.get("#cogitoStatus").should("contain", "Questions ready");
+ cy.get("#cogitoQuestionList .cogitoQuestionItem").should("have.length", 3);
+ cy.get("#cogitoQuestionList .cogitoQuestionText")
+ .eq(0)
+ .should("contain", "What problem does local-first editing solve here?");
+
+ cy.window().then((win) => {
+ expect(win.__cogitoCreateEngineCalls).to.deep.equal(["Qwen3-8B-q4f16_1-MLC"]);
+ expect(win.__cogitoCompletions).to.have.length(1);
+ expect(win.__cogitoCompletions[0].messages[1].content).to.contain(markdown);
});
+
+ cy.contains("#cogitoQuestionList .cogitoInsertBtn", "Insert").first().click();
+ cy.get("#statusBadge").should("contain", "Inserted AI question");
+ cy.get("#editor").should("have.value", `${markdown}> ### AI\nWhat problem does local-first editing solve here?\n`);
});
});
@@ -30094,8 +30315,11 @@ describe("editor view mode toggle", () => {
});
-
-export {};
+
+import "./commands";
+import "@cypress/code-coverage/support";
+
+Cypress.on("uncaught:exception", () => false);
@@ -30700,83 +30924,6 @@ The project lacks consistent code quality enforcement. Adding ESLint will help c
- [x] 1.5 Integrate lint check into build process
-
-## ADDED Requirements
-### Requirement: Canonical Project Logo Asset
-The repository SHALL store a canonical project logo source file that is used for documentation and favicon generation.
-
-#### Scenario: Canonical logo source is available
-- **WHEN** a maintainer inspects branding files in the repository
-- **THEN** a single canonical logo source exists in a stable project path
-- **AND** the source is suitable for deriving README and favicon assets
-
-### Requirement: README Logo Rendering
-The project documentation SHALL render the project logo in `README.md` using repository-relative asset references.
-
-#### Scenario: README displays logo
-- **WHEN** `README.md` is rendered by a GitHub-compatible markdown renderer
-- **THEN** the logo is visible near the document header
-- **AND** the logo reference resolves without external network dependencies
-
-### Requirement: Favicon Assets Derived from Logo
-The project SHALL provide favicon assets derived from the canonical logo source.
-
-#### Scenario: Favicon assets are generated and available
-- **WHEN** the favicon generation workflow is run
-- **THEN** favicon files are produced in the documented output location
-- **AND** generated filenames match those referenced by the application template
-
-### Requirement: Application Template Favicon References
-The application HTML template SHALL reference project favicon assets so browser tabs display the project icon.
-
-#### Scenario: Browser tab uses project favicon
-- **WHEN** a user opens the built application in a modern browser
-- **THEN** the browser resolves favicon references from the application output
-- **AND** the tab icon reflects the project logo branding
-
-
-
-# Change: Add Project Logo to README and Favicon Assets
-
-## Why
-The repository currently lacks consistent branding assets in project documentation and browser metadata. Adding the provided logo to `README.md` and defining favicon outputs improves recognizability and presentation.
-
-## What Changes
-- Add a canonical logo asset in the repository based on `~/Downloads/ink_logo.svg`.
-- Update `README.md` to render the project logo near the top of the document.
-- Define and implement favicon outputs derived from the same logo source.
-- Wire favicon references into the app HTML template so browser tabs use project branding.
-- Document the asset location and favicon generation/update workflow.
-
-## Impact
-- Affected specs: `branding-assets`
-- Affected code:
- - `README.md`
- - `ink.template.html`
- - `build/` scripts (if favicon generation is automated in build)
- - `dist/` favicon artifacts
- - `assets/` branding source files (new)
-
-
-
-## 1. Asset Setup
-- [x] 1.1 Add the provided logo SVG to a canonical repository path for shared branding usage.
-- [x] 1.2 Define favicon output formats and filenames derived from the canonical logo source.
-
-## 2. Documentation and Template Integration
-- [x] 2.1 Update `README.md` to display the project logo with stable relative linking.
-- [x] 2.2 Update the HTML template to include favicon references that resolve in the built output.
-
-## 3. Build and Distribution
-- [x] 3.1 Implement or document the process to generate favicon artifacts from the SVG source.
-- [x] 3.2 Ensure generated favicon assets are available in expected output locations.
-
-## 4. Validation
-- [x] 4.1 Verify `README.md` renders the logo correctly on GitHub-compatible markdown viewers.
-- [x] 4.2 Verify the built app/tab displays the new favicon in a modern browser.
-- [x] 4.3 Verify documentation describes how to refresh logo-derived assets when the SVG changes.
-
-
## ADDED Requirements
@@ -31765,218 +31912,61 @@ The system SHALL provide a linting and review tool for markdown documents that a
#### Scenario: Check style quality
- **WHEN** the system analyzes prose
-- **THEN** it identifies spelling errors and style consistency issues
-
-#### Scenario: Export suggestions as markdown
-- **WHEN** the user requests export
-- **THEN** the system outputs the suggestions in markdown format
-- **AND** the export includes a concise summary, prioritized fixes, and section-level detail
-
-
-
-# Change: Improve Document Linter Output Quality
-
-## Why
-The current Document Linter output is technically correct but too generic to be useful on real study and writing notes. It over-flags list items as long sentences, repeats low-signal style warnings, and does not surface a clear priority order or a concise summary that helps the reader act quickly.
-
-## What Changes
-- Upgrade the linter report to prioritize the most useful feedback first
-- Make sentence and paragraph analysis markdown-aware so list items, headings, and quoted callouts are not misclassified as generic prose
-- Add a stronger summary layer that highlights the top issues, strongest sections, and what to fix first
-- Add section-level analysis so the linter can reason about headings, label-style section starts, and dense sub-parts of a document
-- Reduce dull, repetitive phrasing in favor of more specific, content-aware suggestions
-- Keep the report export in markdown, but make the exported structure more editorial and easier to scan
-
-## Impact
-- Affected specs: document-linter
-- Affected code: Document Linter analysis pipeline, markdown report builder, export output
-- Runtime dependency: None
-- Breaking changes: none
-
-
-
-## 1. Implementation
-- [x] 1.1 Improve markdown-aware parsing so headings, bullets, and callouts are not treated like plain prose
-- [x] 1.2 Rewrite the report builder to include a concise summary, top issues, and prioritized fixes
-- [x] 1.3 Replace generic suggestion phrasing with more specific, content-aware language
-- [x] 1.4 Keep export output in markdown while making it more readable and action-oriented
-- [x] 1.5 Add tests that cover the new report structure and the markdown-aware classification rules
-
-
-
-# Change: Migrate marked to npm Dependency
-
-## Why
-The `marked` library is currently inlined as a minified script block inside `ink.template.html`, manually downloaded from jsDelivr. This makes updates error-prone (requires manual copy-paste of minified code), bloats the template file, and prevents TypeScript from using typed imports. Since the project already uses esbuild for bundling, adding `marked` as an npm dependency is the correct approach: esbuild will bundle it into `dist/app.min.js` at build time, preserving the self-contained nature of the final output with no runtime external dependencies.
-
-## What Changes
-- Add `marked` as an npm dependency.
-- Replace `window.marked` usages in `src/app/bootstrap.ts` with a direct TypeScript import.
-- Remove the inlined `