From 2a5d7734b5b37b45ca679bd5de47fbe718caf867 Mon Sep 17 00:00:00 2001 From: Abraham Date: Wed, 22 Jul 2026 12:43:37 -0700 Subject: [PATCH 1/2] chore: rename mermaid-wysiwyg to visimer (#2843) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: rename mermaid-wysiwyg to visimer Directory public/mermaid-wysiwyg is now public/visimer. NPM packages move from @inkeep/mermaid-wysiwyg-{core,dom,react,codemirror,monaco} to scoped @visimer/{core,dom,react,codemirror,monaco}. Copybara mirror target moves from inkeep/mermaid-wysiwyg to inkeep/visimer. Also reverts the earlier site→mermaidraw rename inside the subtree: apps/site stays apps/site (it's the demo site, not the library). OK consumer stays on @inkeep/mermaid-wysiwyg-{core,dom}@0.1.0 for now; follow-up once @visimer/* publishes to npm. * fix(check-pre-push-mode.test): sort SUBTREES expected list after visimer rename alphabetical order puts public/open-knowledge before public/visimer. GitOrigin-RevId: 7ab9082a77d8333529e255280de2321e0e35fcf0 --- .github/scripts/bridge-public-pr-to-monorepo.mjs | 2 +- knip.config.ts | 4 +++- packages/core/src/bridge/bind-frontmatter-doc.ts | 1 + packages/core/src/bridge/bridge-invariant.ts | 1 + packages/core/src/bridge/doc-boundary-space.ts | 1 + packages/core/src/bridge/growth-detect.ts | 1 + packages/core/src/bridge/merge-three-way.ts | 1 + packages/core/src/bridge/normalize.ts | 1 + packages/core/src/bridge/pm-structural-equivalence.ts | 3 +++ packages/core/src/bridge/subsequence.ts | 1 + packages/core/src/markdown/callout-transformer.ts | 1 + packages/core/src/markdown/comment-promoter.ts | 1 + packages/core/src/markdown/dedent-block-jsx-close.ts | 1 + packages/core/src/markdown/details-accordion-promoter.ts | 1 + packages/core/src/markdown/fence-regions.ts | 1 + packages/core/src/markdown/fixtures/index.ts | 6 ++++++ packages/core/src/markdown/fixtures/perf/generate.ts | 5 +++++ packages/core/src/markdown/guard-flanking-matrix.ts | 1 + packages/core/src/markdown/handler-shadow-audit.ts | 1 + packages/core/src/markdown/highlight-promoter.ts | 1 + packages/core/src/markdown/html-to-mdast.ts | 4 +++- packages/core/src/markdown/image-promoter.ts | 1 + packages/core/src/markdown/index.ts | 9 +++++++++ packages/core/src/markdown/lint/config-files.ts | 1 + packages/core/src/markdown/lint/config-schemas.ts | 1 + packages/core/src/markdown/lint/default-config.ts | 1 + packages/core/src/markdown/lint/index.ts | 1 + packages/core/src/markdown/lint/plugins.ts | 2 ++ packages/core/src/markdown/lint/types.ts | 2 ++ packages/core/src/markdown/math-promoter.ts | 1 + packages/core/src/markdown/mdast-to-hast-handlers.ts | 1 + packages/core/src/markdown/mdast-to-html.ts | 1 + packages/core/src/markdown/merged-walker.ts | 1 + packages/core/src/markdown/mermaid-promoter.ts | 1 + packages/core/src/markdown/parse-with-fallback.ts | 5 +++++ packages/core/src/markdown/parser-drop-closure.ts | 1 + packages/core/src/markdown/pipeline.ts | 1 + packages/core/src/markdown/position-aware-join.ts | 1 + packages/core/src/markdown/position-slice.ts | 4 +++- .../markdown/rehype-plugins/skip-notion-whitespace.ts | 1 + .../core/src/markdown/rehype-plugins/strip-cocoa-meta.ts | 1 + .../src/markdown/rehype-plugins/strip-gdocs-wrapper.ts | 1 + .../markdown/rehype-plugins/strip-github-hovercard.ts | 1 + .../src/markdown/rehype-plugins/strip-gmail-classes.ts | 1 + .../src/markdown/rehype-plugins/strip-gsheets-wrapper.ts | 1 + .../core/src/markdown/rehype-plugins/strip-mso-styles.ts | 1 + .../src/markdown/rehype-plugins/strip-slack-classes.ts | 1 + .../src/markdown/rehype-plugins/strip-vscode-spans.ts | 1 + packages/core/src/markdown/resolve-image-url.ts | 1 + packages/core/src/markdown/safe-url.ts | 1 + packages/core/src/markdown/serialize-helpers.ts | 1 + .../core/src/markdown/single-dollar-math-promoter.ts | 1 + packages/core/src/markdown/to-markdown-handlers.ts | 1 + packages/core/src/markdown/unknown-mdast-guard.ts | 1 + packages/core/src/markdown/whitespace-char-ref.ts | 1 + packages/core/src/markdown/wiki-link-micromark.ts | 3 +++ 56 files changed, 89 insertions(+), 4 deletions(-) diff --git a/.github/scripts/bridge-public-pr-to-monorepo.mjs b/.github/scripts/bridge-public-pr-to-monorepo.mjs index 3ff3b1755..1c3958f60 100644 --- a/.github/scripts/bridge-public-pr-to-monorepo.mjs +++ b/.github/scripts/bridge-public-pr-to-monorepo.mjs @@ -11,7 +11,7 @@ import { applyClaGate } from './cla-gate.mjs'; // Sibling bridge copies: // - public/agents/.github/scripts/bridge-public-pr-to-monorepo.mjs // - public/agents-optional-local-dev/.github/scripts/bridge-public-pr-to-monorepo.mjs -// - public/mermaid-wysiwyg/.github/scripts/bridge-public-pr-to-monorepo.mjs +// - public/visimer/.github/scripts/bridge-public-pr-to-monorepo.mjs // The Open Knowledge copy additionally imports a co-located `cla-gate.mjs` for // contributor-CLA enforcement — an OK-only divergence. That module ships to the // same repo via Copybara, so the import resolves on the mirror; the "no shared diff --git a/knip.config.ts b/knip.config.ts index 296751a02..6e3847861 100644 --- a/knip.config.ts +++ b/knip.config.ts @@ -121,7 +121,9 @@ export default { 'packages/server': { entry: ['src/**/*.test.ts', 'src/parse-worker.ts'], project: 'src/**', - ignoreDependencies: ['@types/shell-quote'], + ignoreDependencies: [ + '@types/shell-quote', + ], }, 'packages/cli': { entry: ['src/**/*.test.ts', 'scripts/*.ts', 'tests/**/*.ts', 'src/parse-worker.ts'], diff --git a/packages/core/src/bridge/bind-frontmatter-doc.ts b/packages/core/src/bridge/bind-frontmatter-doc.ts index c014b1413..8f9d95287 100644 --- a/packages/core/src/bridge/bind-frontmatter-doc.ts +++ b/packages/core/src/bridge/bind-frontmatter-doc.ts @@ -1,3 +1,4 @@ + import type * as Y from 'yjs'; import type { Err, Ok, Result } from '../config/result.ts'; import { type FrontmatterValidationError, toFrontmatterIssue } from '../frontmatter/errors.ts'; diff --git a/packages/core/src/bridge/bridge-invariant.ts b/packages/core/src/bridge/bridge-invariant.ts index 005627195..8bfdb40cd 100644 --- a/packages/core/src/bridge/bridge-invariant.ts +++ b/packages/core/src/bridge/bridge-invariant.ts @@ -1,3 +1,4 @@ + import { fnv1aDigest } from './hash-util.ts'; export type BridgeInvariantSite = 'observer-b' | 'persistence' | 'test-harness'; diff --git a/packages/core/src/bridge/doc-boundary-space.ts b/packages/core/src/bridge/doc-boundary-space.ts index 8890f6739..c18c086d5 100644 --- a/packages/core/src/bridge/doc-boundary-space.ts +++ b/packages/core/src/bridge/doc-boundary-space.ts @@ -1,3 +1,4 @@ + import { FRONTMATTER_RE, stripFrontmatter } from '../extensions/frontmatter.ts'; const LEADING_BOUNDARY_RE = /^(?:\r?\n)+/; diff --git a/packages/core/src/bridge/growth-detect.ts b/packages/core/src/bridge/growth-detect.ts index 9d9c244b0..463e89595 100644 --- a/packages/core/src/bridge/growth-detect.ts +++ b/packages/core/src/bridge/growth-detect.ts @@ -1,3 +1,4 @@ + const DEFAULT_MIN_SUBSTANTIVE_LINE_LENGTH = 16; export const DUPLICATION_GATE_MIN_LINE_LENGTH = 8; diff --git a/packages/core/src/bridge/merge-three-way.ts b/packages/core/src/bridge/merge-three-way.ts index 3ab9cc6ef..3c927eb47 100644 --- a/packages/core/src/bridge/merge-three-way.ts +++ b/packages/core/src/bridge/merge-three-way.ts @@ -38,6 +38,7 @@ function mergeThreeWayImpl(baseline: string, userText: string, agentText: string return parts.join('\n'); } + export type BridgeMergeContentLossSide = 'user' | 'agent'; export type BridgeMergeContentLossWhich = 'substring' | 'order' | 'growth'; diff --git a/packages/core/src/bridge/normalize.ts b/packages/core/src/bridge/normalize.ts index 4b8890476..3f0880aa5 100644 --- a/packages/core/src/bridge/normalize.ts +++ b/packages/core/src/bridge/normalize.ts @@ -1,3 +1,4 @@ + const COMMONMARK_ESCAPE_RE = /\\([!"#$%&'()*+,\-./:;<=>?@[\]^_`{|}~])/g; const TABLE_ALIGN_ROW_RE = /^\s*\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)+\|?\s*$/; diff --git a/packages/core/src/bridge/pm-structural-equivalence.ts b/packages/core/src/bridge/pm-structural-equivalence.ts index 754dc5cc2..0be5203e4 100644 --- a/packages/core/src/bridge/pm-structural-equivalence.ts +++ b/packages/core/src/bridge/pm-structural-equivalence.ts @@ -32,6 +32,7 @@ const BR_SENTINEL: PmStructuralNode = { type: '__br__' }; const BR_LITERAL_RE = /^$/i; + interface DegradeEntry { readonly label: string; readonly severity: ToleranceClassSeverity; @@ -127,6 +128,7 @@ export interface ComparePmStructuralOptions { ignoreAttrs?: (attrKey: string) => boolean; } + /** Rebuild a node with `fn` applied to each child; identity when childless. * Shared by the degrade canonicalizers so none re-implements the walk. */ function mapChildren( @@ -248,6 +250,7 @@ function applyDegrades(tree: PmStructuralNode): { return { normalized: current, fired }; } + export function comparePmStructural( expected: PmStructuralNode, actual: PmStructuralNode, diff --git a/packages/core/src/bridge/subsequence.ts b/packages/core/src/bridge/subsequence.ts index af20de1e2..dca644141 100644 --- a/packages/core/src/bridge/subsequence.ts +++ b/packages/core/src/bridge/subsequence.ts @@ -1,3 +1,4 @@ + /** True when `needle` is a subsequence of `haystack` (two-pointer, O(n+m)): * insertions in `haystack` are free; any dropped or substituted `needle` byte * fails. */ diff --git a/packages/core/src/markdown/callout-transformer.ts b/packages/core/src/markdown/callout-transformer.ts index 83d47404a..a363dfd54 100644 --- a/packages/core/src/markdown/callout-transformer.ts +++ b/packages/core/src/markdown/callout-transformer.ts @@ -1,3 +1,4 @@ + import type { Blockquote, Paragraph, Root } from 'mdast'; import type { MdxJsxAttribute, MdxJsxFlowElement } from 'mdast-util-mdx'; import { visit } from 'unist-util-visit'; diff --git a/packages/core/src/markdown/comment-promoter.ts b/packages/core/src/markdown/comment-promoter.ts index 193e286b0..47f0d07e4 100644 --- a/packages/core/src/markdown/comment-promoter.ts +++ b/packages/core/src/markdown/comment-promoter.ts @@ -1,3 +1,4 @@ + import type { Nodes, Paragraph, PhrasingContent, Root, RootContent, Text } from 'mdast'; import { SKIP, visit } from 'unist-util-visit'; import type { VFile } from 'vfile'; diff --git a/packages/core/src/markdown/dedent-block-jsx-close.ts b/packages/core/src/markdown/dedent-block-jsx-close.ts index ece5c02a1..b6299a948 100644 --- a/packages/core/src/markdown/dedent-block-jsx-close.ts +++ b/packages/core/src/markdown/dedent-block-jsx-close.ts @@ -1,3 +1,4 @@ + import { findFencedRegions, isInsideFence } from './fence-regions.ts'; const INDENTED_BLOCK_JSX_CLOSE_RE = /^([ ]{1,3})(<\/[A-Z][A-Za-z0-9_]*\s*>)([ \t]*)$/gm; diff --git a/packages/core/src/markdown/details-accordion-promoter.ts b/packages/core/src/markdown/details-accordion-promoter.ts index 54774764a..acd398aa6 100644 --- a/packages/core/src/markdown/details-accordion-promoter.ts +++ b/packages/core/src/markdown/details-accordion-promoter.ts @@ -1,3 +1,4 @@ + import type { Nodes, Paragraph, Parent, Root, Text } from 'mdast'; import type { MdxJsxAttribute, MdxJsxFlowElement } from 'mdast-util-mdx'; import { visit } from 'unist-util-visit'; diff --git a/packages/core/src/markdown/fence-regions.ts b/packages/core/src/markdown/fence-regions.ts index c7518d614..fb5ac8e8a 100644 --- a/packages/core/src/markdown/fence-regions.ts +++ b/packages/core/src/markdown/fence-regions.ts @@ -1,3 +1,4 @@ + const FENCE_RE = /^(`{3,}|~{3,})/gm; export function findFencedRegions(src: string): Array<[number, number]> { diff --git a/packages/core/src/markdown/fixtures/index.ts b/packages/core/src/markdown/fixtures/index.ts index 00f5771ee..6931623c7 100644 --- a/packages/core/src/markdown/fixtures/index.ts +++ b/packages/core/src/markdown/fixtures/index.ts @@ -1,3 +1,4 @@ + import { readFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -8,6 +9,7 @@ function fixturePath(...segments: string[]): string { return resolve(FIXTURES_DIR, ...segments); } + interface GfmExample { section: string; markdown: string; @@ -17,6 +19,7 @@ export function loadGfmExamples(): GfmExample[] { return JSON.parse(readFileSync(fixturePath('gfm', 'examples.json'), 'utf8')) as GfmExample[]; } + interface MdxCrashEntry { id: string; input: string; @@ -68,6 +71,7 @@ export function loadLargeEmbedFixtures(): LargeEmbedFixture[] { ) as LargeEmbedFixture[]; } + export function loadPrd6955Before(): string { return readFileSync(fixturePath('regression', 'prd-6955-before.md'), 'utf8'); } @@ -76,6 +80,7 @@ export function loadPrd6955CorruptedTriplicated(): string { return readFileSync(fixturePath('regression', 'prd-6955-corrupted-triplicated.md'), 'utf8'); } + export interface NgPinnedCase { id: string; name: string; @@ -92,6 +97,7 @@ export function loadNgPinnedCases(): NgPinnedCase[] { ) as NgPinnedCase[]; } + export function loadLargeRealistic(): string { return readFileSync(fixturePath('perf', 'large-realistic.md'), 'utf8'); } diff --git a/packages/core/src/markdown/fixtures/perf/generate.ts b/packages/core/src/markdown/fixtures/perf/generate.ts index 8b1eb8519..daa3ef82b 100644 --- a/packages/core/src/markdown/fixtures/perf/generate.ts +++ b/packages/core/src/markdown/fixtures/perf/generate.ts @@ -1,3 +1,4 @@ + import { writeFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -10,6 +11,7 @@ const BASELINE_ONLY_COUNTS = [500, 2500] as const; const SEED = 0xf1de1117; + function mulberry32(seed: number): () => number { let a = seed >>> 0; return () => { @@ -36,6 +38,7 @@ function pickWeighted(rand: () => number, items: readonly [T, number][]): T { return items[items.length - 1][0]; } + const WORDS = [ 'lorem', 'ipsum', @@ -153,6 +156,7 @@ function mdxBlock(rand: () => number): string { return `<${name}>\n\n${sentence(rand)}\n\n`; } + type BlockKind = 'paragraph' | 'heading' | 'list' | 'code' | 'table' | 'mdx'; const MIX: readonly [BlockKind, number][] = [ @@ -191,6 +195,7 @@ function generateDocument(blockCount: number, seed: number): string { return `${blocks.join('\n\n')}\n`; } + function main(): void { for (const count of [...BLOCK_COUNTS, ...BASELINE_ONLY_COUNTS]) { const doc = generateDocument(count, SEED); diff --git a/packages/core/src/markdown/guard-flanking-matrix.ts b/packages/core/src/markdown/guard-flanking-matrix.ts index 420dc0fe5..32c055c5b 100644 --- a/packages/core/src/markdown/guard-flanking-matrix.ts +++ b/packages/core/src/markdown/guard-flanking-matrix.ts @@ -7,6 +7,7 @@ import { import { BACKSLASH_GUARD_SUBSTITUTIONS, encodeBackslashEscapes } from './backslash-escape-guard.ts'; import { ENTITY_REF_GUARD_SUBSTITUTIONS, encodeEntityRefs } from './entity-ref-guard.ts'; + export const ATTENTION_DELIMITERS = ['*', '_', '**', '~~', '=='] as const; export type FlankClass = 'whitespace' | 'punctuation' | 'other'; diff --git a/packages/core/src/markdown/handler-shadow-audit.ts b/packages/core/src/markdown/handler-shadow-audit.ts index 2653473da..283913255 100644 --- a/packages/core/src/markdown/handler-shadow-audit.ts +++ b/packages/core/src/markdown/handler-shadow-audit.ts @@ -1,3 +1,4 @@ + export interface HandlerShadowWitness { input: string; expect: 'byte' | 'byte-and-reparse-type'; diff --git a/packages/core/src/markdown/highlight-promoter.ts b/packages/core/src/markdown/highlight-promoter.ts index f55912407..6319f264f 100644 --- a/packages/core/src/markdown/highlight-promoter.ts +++ b/packages/core/src/markdown/highlight-promoter.ts @@ -1,3 +1,4 @@ + import type { Nodes, Parent, PhrasingContent, Root, Text } from 'mdast'; import type { MdxJsxTextElement } from 'mdast-util-mdx'; import { SKIP, visit } from 'unist-util-visit'; diff --git a/packages/core/src/markdown/html-to-mdast.ts b/packages/core/src/markdown/html-to-mdast.ts index b1f252c31..8dc3a81b5 100644 --- a/packages/core/src/markdown/html-to-mdast.ts +++ b/packages/core/src/markdown/html-to-mdast.ts @@ -1,3 +1,4 @@ + import type { Root as HastRoot } from 'hast'; import type { List, Root as MdastRoot } from 'mdast'; import rehypeParse from 'rehype-parse'; @@ -85,7 +86,8 @@ export function htmlToMdast(html: string, options?: HtmlToMdastOptions): MdastRo throw new HtmlPayloadTooLargeError(html.length, maxBytes); } - const processor = unified().use(rehypeParse, { fragment: true }); + const processor = unified() + .use(rehypeParse, { fragment: true }); for (const plugin of cleanupPlugins) { processor.use(plugin); diff --git a/packages/core/src/markdown/image-promoter.ts b/packages/core/src/markdown/image-promoter.ts index 706fa9afb..994eb6917 100644 --- a/packages/core/src/markdown/image-promoter.ts +++ b/packages/core/src/markdown/image-promoter.ts @@ -1,3 +1,4 @@ + import type { Image, Paragraph, Root } from 'mdast'; import type { MdxJsxAttribute, MdxJsxFlowElement } from 'mdast-util-mdx'; import { visit } from 'unist-util-visit'; diff --git a/packages/core/src/markdown/index.ts b/packages/core/src/markdown/index.ts index 8b36807fd..fc30bda02 100644 --- a/packages/core/src/markdown/index.ts +++ b/packages/core/src/markdown/index.ts @@ -1,3 +1,4 @@ + import { type FromProseMirrorOptions, fromPmMark, @@ -196,6 +197,7 @@ export class MarkdownManager { } } + const registry = createRegistry(); function destructureAttrs( @@ -244,6 +246,7 @@ function destructureAttrs( return result; } + function hasDirtyDescendant(node: PmNode): boolean { let found = false; node.descendants((child) => { @@ -263,6 +266,7 @@ function effectiveDirty(node: PmNode, freshnessChecker?: StructuralFreshnessChec return freshnessChecker ? freshnessChecker.isDiverged(node.toJSON()) : false; } + function isEmptyMdastParagraph(node: MdastNodes): boolean { if (node.type !== 'paragraph') return false; const children = node.children ?? []; @@ -331,6 +335,7 @@ function extractTextFromMdastNodes(nodes: MdastNodes[]): string { return out; } + import { AUDIO_EXTENSIONS, FILE_ATTACHMENT_EXTENSIONS, @@ -577,6 +582,7 @@ function buildMdastToPmHandlers( })); } + if (m.emphasis) { handlers.emphasis = toPmMark(m.emphasis, (node: Emphasis) => ({ sourceDelimiter: node.data?.sourceDelimiter ?? '*', @@ -657,6 +663,7 @@ function buildMdastToPmHandlers( })); } + if (m.link) { const sourceLiteralMark = m.sourceLiteral; handlers.link = (node: Link, _parent: MdastParent, state: MdastToPmState) => { @@ -935,6 +942,7 @@ function buildMdastToPmHandlers( }; } + const blockUnknownHandler = (node: { type: string; position?: { start: { offset: number }; end: { offset: number } }; @@ -1060,6 +1068,7 @@ function buildMdastToPmHandlers( return handlers as RemarkProseMirrorOptions['handlers']; } + function buildPmToMdastHandlers( schema: Schema, freshness: FreshnessCheckerHolder, diff --git a/packages/core/src/markdown/lint/config-files.ts b/packages/core/src/markdown/lint/config-files.ts index 312c9c74b..94b34dee1 100644 --- a/packages/core/src/markdown/lint/config-files.ts +++ b/packages/core/src/markdown/lint/config-files.ts @@ -1,3 +1,4 @@ + const MARKDOWNLINT_JSON_CONFIG_FILES: ReadonlySet = new Set([ '.markdownlint.json', '.markdownlint.jsonc', diff --git a/packages/core/src/markdown/lint/config-schemas.ts b/packages/core/src/markdown/lint/config-schemas.ts index e78a1381d..207cd95c2 100644 --- a/packages/core/src/markdown/lint/config-schemas.ts +++ b/packages/core/src/markdown/lint/config-schemas.ts @@ -29,6 +29,7 @@ const fullPluginShape = Object.fromEntries( LINT_PLUGINS.map((plugin) => [plugin.id, plugin.sliceSchema]), ) as z.ZodRawShape; + export const LinterConfigSchema = z.object({ enabled: z.boolean(), plugins: z.object(fullPluginShape), diff --git a/packages/core/src/markdown/lint/default-config.ts b/packages/core/src/markdown/lint/default-config.ts index d48eea5e4..a51f2ffb0 100644 --- a/packages/core/src/markdown/lint/default-config.ts +++ b/packages/core/src/markdown/lint/default-config.ts @@ -6,6 +6,7 @@ export const DEFAULT_MARKDOWNLINT_CONFIG: Record | undefined, ): Configuration { diff --git a/packages/core/src/markdown/lint/index.ts b/packages/core/src/markdown/lint/index.ts index 8a154ce30..d763d8d28 100644 --- a/packages/core/src/markdown/lint/index.ts +++ b/packages/core/src/markdown/lint/index.ts @@ -1,3 +1,4 @@ + import { LINT_PLUGINS, type LinterConfig } from './plugins.ts'; import type { LintDiagnostic } from './types.ts'; diff --git a/packages/core/src/markdown/lint/plugins.ts b/packages/core/src/markdown/lint/plugins.ts index 1cd40ef3a..65a1b564c 100644 --- a/packages/core/src/markdown/lint/plugins.ts +++ b/packages/core/src/markdown/lint/plugins.ts @@ -1,3 +1,4 @@ + import { z } from 'zod'; import { DEFAULT_MARKDOWNLINT_CONFIG, resolveMarkdownlintConfig } from './default-config.ts'; import { fixMarkdownText, runMarkdownlint } from './markdownlint-runner.ts'; @@ -8,6 +9,7 @@ import { type MarkdownlintSlice, } from './types.ts'; + const MarkdownlintRuleSettingSchema = z.union([ z.boolean(), z.enum(MARKDOWNLINT_RULE_SEVERITIES), diff --git a/packages/core/src/markdown/lint/types.ts b/packages/core/src/markdown/lint/types.ts index 617f6e5b9..3651bd7eb 100644 --- a/packages/core/src/markdown/lint/types.ts +++ b/packages/core/src/markdown/lint/types.ts @@ -1,3 +1,4 @@ + export const LINT_PLUGIN_IDS = ['markdownlint'] as const; export type LintPluginId = (typeof LINT_PLUGIN_IDS)[number]; @@ -41,6 +42,7 @@ export interface MarkdownlintSlice { rules: Record; } + interface RuleOptionSpecBase { key: string; description: string; diff --git a/packages/core/src/markdown/math-promoter.ts b/packages/core/src/markdown/math-promoter.ts index a5b511f25..bd79424c7 100644 --- a/packages/core/src/markdown/math-promoter.ts +++ b/packages/core/src/markdown/math-promoter.ts @@ -1,3 +1,4 @@ + import type { Code, Root } from 'mdast'; import type { Math as MdastMath } from 'mdast-util-math'; import type { MdxJsxAttribute, MdxJsxFlowElement } from 'mdast-util-mdx'; diff --git a/packages/core/src/markdown/mdast-to-hast-handlers.ts b/packages/core/src/markdown/mdast-to-hast-handlers.ts index 679c72ef7..f84967798 100644 --- a/packages/core/src/markdown/mdast-to-hast-handlers.ts +++ b/packages/core/src/markdown/mdast-to-hast-handlers.ts @@ -1,3 +1,4 @@ + import type { Comment, Element, ElementContent, Properties } from 'hast'; import type { FootnoteDefinition, FootnoteReference } from 'mdast'; import type { MdxJsxFlowElement, MdxJsxTextElement } from 'mdast-util-mdx'; diff --git a/packages/core/src/markdown/mdast-to-html.ts b/packages/core/src/markdown/mdast-to-html.ts index 8ce2b2492..df788a80d 100644 --- a/packages/core/src/markdown/mdast-to-html.ts +++ b/packages/core/src/markdown/mdast-to-html.ts @@ -1,3 +1,4 @@ + import type { Element, Root as HastRoot, Text as HastText } from 'hast'; import type { Root as MdastRoot, Text as MdastText } from 'mdast'; import type { Handler } from 'mdast-util-to-hast'; diff --git a/packages/core/src/markdown/merged-walker.ts b/packages/core/src/markdown/merged-walker.ts index bfd7e3109..fa3cfcd86 100644 --- a/packages/core/src/markdown/merged-walker.ts +++ b/packages/core/src/markdown/merged-walker.ts @@ -1,3 +1,4 @@ + import type { Nodes, Parent, Root } from 'mdast'; import { SKIP, visit } from 'unist-util-visit'; import type { VFile } from 'vfile'; diff --git a/packages/core/src/markdown/mermaid-promoter.ts b/packages/core/src/markdown/mermaid-promoter.ts index 1a897fca8..17ed2b829 100644 --- a/packages/core/src/markdown/mermaid-promoter.ts +++ b/packages/core/src/markdown/mermaid-promoter.ts @@ -1,3 +1,4 @@ + import type { Code, Root } from 'mdast'; import type { MdxJsxAttribute, MdxJsxFlowElement } from 'mdast-util-mdx'; import { visit } from 'unist-util-visit'; diff --git a/packages/core/src/markdown/parse-with-fallback.ts b/packages/core/src/markdown/parse-with-fallback.ts index 9864b3b60..7e4a9a48b 100644 --- a/packages/core/src/markdown/parse-with-fallback.ts +++ b/packages/core/src/markdown/parse-with-fallback.ts @@ -168,6 +168,7 @@ export function parseRecursive( } } + interface VFilePlace { offset?: number; start?: { offset?: number }; @@ -186,6 +187,7 @@ function extractErrorOffset(err: unknown): number | undefined { return undefined; } + interface Region { start: number; end: number; @@ -209,6 +211,7 @@ function nearestBlankLineAfter(src: string, offset: number): number | null { return null; } + export interface TagEvent { kind: 'open' | 'close' | 'self-close'; name: string; @@ -355,6 +358,7 @@ function findFallbackRegion(src: string, errorOffset: number): Region { return { start: blockStart, end: blockEnd }; } + interface SourceBlock { src: string; start: number; @@ -452,6 +456,7 @@ function tryPerBlockFallback( }; } + function wholeDocRawText(source: string): JSONContent { return { type: 'doc', diff --git a/packages/core/src/markdown/parser-drop-closure.ts b/packages/core/src/markdown/parser-drop-closure.ts index e6ab4fd57..43d420187 100644 --- a/packages/core/src/markdown/parser-drop-closure.ts +++ b/packages/core/src/markdown/parser-drop-closure.ts @@ -1,3 +1,4 @@ + export type DroppedTokenAdjudication = | { kind: 'format-dof-axis'; diff --git a/packages/core/src/markdown/pipeline.ts b/packages/core/src/markdown/pipeline.ts index 7f2d5adca..e9e507469 100644 --- a/packages/core/src/markdown/pipeline.ts +++ b/packages/core/src/markdown/pipeline.ts @@ -1,3 +1,4 @@ + import { type FromProseMirrorOptions, fromProseMirror, diff --git a/packages/core/src/markdown/position-aware-join.ts b/packages/core/src/markdown/position-aware-join.ts index 5a5e4c42a..f8f5e8530 100644 --- a/packages/core/src/markdown/position-aware-join.ts +++ b/packages/core/src/markdown/position-aware-join.ts @@ -1,3 +1,4 @@ + import type { Join } from 'mdast-util-to-markdown'; import type { Position } from 'unist'; diff --git a/packages/core/src/markdown/position-slice.ts b/packages/core/src/markdown/position-slice.ts index 0f399d34c..e5b4c9eba 100644 --- a/packages/core/src/markdown/position-slice.ts +++ b/packages/core/src/markdown/position-slice.ts @@ -1,3 +1,4 @@ + import type { Nodes, Root } from 'mdast'; import { visit } from 'unist-util-visit'; import type { VFile } from 'vfile'; @@ -476,7 +477,8 @@ export function applyPositionSliceToNode( const first = source[startOff]; if (first !== '[' && first !== '<' && !node.title) { node.data.sourceStyle = 'gfm-autolink'; - } else if (first === '[') { + } + else if (first === '[') { const slice = source.slice(startOff, endOff); const closeBracketIdx = slice.lastIndexOf(']('); if (closeBracketIdx !== -1) { diff --git a/packages/core/src/markdown/rehype-plugins/skip-notion-whitespace.ts b/packages/core/src/markdown/rehype-plugins/skip-notion-whitespace.ts index db10fa54b..ca225ce9b 100644 --- a/packages/core/src/markdown/rehype-plugins/skip-notion-whitespace.ts +++ b/packages/core/src/markdown/rehype-plugins/skip-notion-whitespace.ts @@ -1,3 +1,4 @@ + import type { Comment, Element, Root, Text } from 'hast'; import type { Plugin } from 'unified'; diff --git a/packages/core/src/markdown/rehype-plugins/strip-cocoa-meta.ts b/packages/core/src/markdown/rehype-plugins/strip-cocoa-meta.ts index 34068d103..3a2524a9a 100644 --- a/packages/core/src/markdown/rehype-plugins/strip-cocoa-meta.ts +++ b/packages/core/src/markdown/rehype-plugins/strip-cocoa-meta.ts @@ -1,3 +1,4 @@ + import type { Element, ElementContent, Root } from 'hast'; import type { Plugin } from 'unified'; diff --git a/packages/core/src/markdown/rehype-plugins/strip-gdocs-wrapper.ts b/packages/core/src/markdown/rehype-plugins/strip-gdocs-wrapper.ts index 93151d43b..2279a0189 100644 --- a/packages/core/src/markdown/rehype-plugins/strip-gdocs-wrapper.ts +++ b/packages/core/src/markdown/rehype-plugins/strip-gdocs-wrapper.ts @@ -1,3 +1,4 @@ + import type { Element, Root } from 'hast'; import type { Plugin } from 'unified'; diff --git a/packages/core/src/markdown/rehype-plugins/strip-github-hovercard.ts b/packages/core/src/markdown/rehype-plugins/strip-github-hovercard.ts index 188f02d83..a0f467c42 100644 --- a/packages/core/src/markdown/rehype-plugins/strip-github-hovercard.ts +++ b/packages/core/src/markdown/rehype-plugins/strip-github-hovercard.ts @@ -1,3 +1,4 @@ + import type { Element, Root } from 'hast'; import type { Plugin } from 'unified'; diff --git a/packages/core/src/markdown/rehype-plugins/strip-gmail-classes.ts b/packages/core/src/markdown/rehype-plugins/strip-gmail-classes.ts index 4ba2c3418..4e8542a21 100644 --- a/packages/core/src/markdown/rehype-plugins/strip-gmail-classes.ts +++ b/packages/core/src/markdown/rehype-plugins/strip-gmail-classes.ts @@ -1,3 +1,4 @@ + import type { Element, Root } from 'hast'; import type { Plugin } from 'unified'; diff --git a/packages/core/src/markdown/rehype-plugins/strip-gsheets-wrapper.ts b/packages/core/src/markdown/rehype-plugins/strip-gsheets-wrapper.ts index 1a74b1272..a3c96b24a 100644 --- a/packages/core/src/markdown/rehype-plugins/strip-gsheets-wrapper.ts +++ b/packages/core/src/markdown/rehype-plugins/strip-gsheets-wrapper.ts @@ -1,3 +1,4 @@ + import type { Element, ElementContent, Root } from 'hast'; import type { Plugin } from 'unified'; diff --git a/packages/core/src/markdown/rehype-plugins/strip-mso-styles.ts b/packages/core/src/markdown/rehype-plugins/strip-mso-styles.ts index de35e7b79..e95cf43d6 100644 --- a/packages/core/src/markdown/rehype-plugins/strip-mso-styles.ts +++ b/packages/core/src/markdown/rehype-plugins/strip-mso-styles.ts @@ -1,3 +1,4 @@ + import type { Comment, Element, ElementContent, Root } from 'hast'; import type { Plugin } from 'unified'; diff --git a/packages/core/src/markdown/rehype-plugins/strip-slack-classes.ts b/packages/core/src/markdown/rehype-plugins/strip-slack-classes.ts index b4be1b2ea..ffdb4d5b5 100644 --- a/packages/core/src/markdown/rehype-plugins/strip-slack-classes.ts +++ b/packages/core/src/markdown/rehype-plugins/strip-slack-classes.ts @@ -1,3 +1,4 @@ + import type { Element, ElementContent, Root } from 'hast'; import type { Plugin } from 'unified'; diff --git a/packages/core/src/markdown/rehype-plugins/strip-vscode-spans.ts b/packages/core/src/markdown/rehype-plugins/strip-vscode-spans.ts index 3c8d88b7c..e2a96d1a7 100644 --- a/packages/core/src/markdown/rehype-plugins/strip-vscode-spans.ts +++ b/packages/core/src/markdown/rehype-plugins/strip-vscode-spans.ts @@ -1,3 +1,4 @@ + import type { Element, Root } from 'hast'; import type { Plugin } from 'unified'; diff --git a/packages/core/src/markdown/resolve-image-url.ts b/packages/core/src/markdown/resolve-image-url.ts index cf2c9da8e..0389575ac 100644 --- a/packages/core/src/markdown/resolve-image-url.ts +++ b/packages/core/src/markdown/resolve-image-url.ts @@ -1,3 +1,4 @@ + import { isRelativeUrl } from './safe-url.ts'; function isDevDiagnosticContext(): boolean { diff --git a/packages/core/src/markdown/safe-url.ts b/packages/core/src/markdown/safe-url.ts index 4d3250d3a..8be632ba4 100644 --- a/packages/core/src/markdown/safe-url.ts +++ b/packages/core/src/markdown/safe-url.ts @@ -1,3 +1,4 @@ + export const SAFE_URL_SCHEMES = ['https', 'http', 'mailto', 'tel', 'ftp', 'sms'] as const; const SCHEME_ALT = SAFE_URL_SCHEMES.map((s) => `${s}:`).join('|'); diff --git a/packages/core/src/markdown/serialize-helpers.ts b/packages/core/src/markdown/serialize-helpers.ts index 46cc6a074..542204403 100644 --- a/packages/core/src/markdown/serialize-helpers.ts +++ b/packages/core/src/markdown/serialize-helpers.ts @@ -1,3 +1,4 @@ + import type { Node as PmNode } from '@tiptap/pm/model'; import type { MdxJsxAttribute, MdxJsxExpressionAttribute, MdxJsxFlowElement } from 'mdast-util-mdx'; import type { PropDef, SerializeContext } from '../registry/types.ts'; diff --git a/packages/core/src/markdown/single-dollar-math-promoter.ts b/packages/core/src/markdown/single-dollar-math-promoter.ts index be97d4d62..fe73659e6 100644 --- a/packages/core/src/markdown/single-dollar-math-promoter.ts +++ b/packages/core/src/markdown/single-dollar-math-promoter.ts @@ -1,3 +1,4 @@ + import type { PhrasingContent, Root, Text } from 'mdast'; import type { InlineMath } from 'mdast-util-math'; import { SKIP, visit } from 'unist-util-visit'; diff --git a/packages/core/src/markdown/to-markdown-handlers.ts b/packages/core/src/markdown/to-markdown-handlers.ts index 8f4e9a41c..7bce04302 100644 --- a/packages/core/src/markdown/to-markdown-handlers.ts +++ b/packages/core/src/markdown/to-markdown-handlers.ts @@ -1,3 +1,4 @@ + import type { Nodes, Parents } from 'mdast'; import type { MdxJsxAttribute, MdxJsxExpressionAttribute, MdxJsxFlowElement } from 'mdast-util-mdx'; import type { Handle, Info, State } from 'mdast-util-to-markdown'; diff --git a/packages/core/src/markdown/unknown-mdast-guard.ts b/packages/core/src/markdown/unknown-mdast-guard.ts index cd245c38c..c5bfd2386 100644 --- a/packages/core/src/markdown/unknown-mdast-guard.ts +++ b/packages/core/src/markdown/unknown-mdast-guard.ts @@ -1,3 +1,4 @@ + import type { Root as MdastRoot } from 'mdast'; import type { VFile } from 'vfile'; diff --git a/packages/core/src/markdown/whitespace-char-ref.ts b/packages/core/src/markdown/whitespace-char-ref.ts index d578476a7..ac9f5239b 100644 --- a/packages/core/src/markdown/whitespace-char-ref.ts +++ b/packages/core/src/markdown/whitespace-char-ref.ts @@ -1,3 +1,4 @@ + const INLINE_WHITESPACE_BY_CODE: ReadonlyMap = new Map([ [0x20, ' '], [0x09, '\t'], diff --git a/packages/core/src/markdown/wiki-link-micromark.ts b/packages/core/src/markdown/wiki-link-micromark.ts index 6dad26200..afd9936fe 100644 --- a/packages/core/src/markdown/wiki-link-micromark.ts +++ b/packages/core/src/markdown/wiki-link-micromark.ts @@ -17,6 +17,7 @@ declare module 'micromark-util-types' { } } + const CODE_BANG = 33; // ! const CODE_LBRACKET = 91; // [ const CODE_RBRACKET = 93; // ] @@ -260,6 +261,7 @@ export function wikiLinkSyntax(): Extension { }; } + function enterWikiLink(this: CompileContext, token: Token) { this.enter( { @@ -385,6 +387,7 @@ export const wikiLinkToMarkdown: { unsafe: [{ character: '[', inConstruct: ['phrasing'] }], }; + const MICROMARK_EXT = wikiLinkSyntax(); export function remarkWikiLink(this: Processor) { From a5a133c42d454e772aed89c0223b2dd792f80074 Mon Sep 17 00:00:00 2001 From: mike-inkeep Date: Wed, 22 Jul 2026 15:58:32 -0400 Subject: [PATCH 2/2] fix(open-knowledge): tear down the `ok ui` sibling when `ok start` exits via a signal (#2795) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes inkeep/open-knowledge#692: `ok start` spawns a detached `ok ui` process for the editor shell. On Ctrl+C (SIGINT/SIGTERM) the CLI destroyed the collab server but never signalled the UI child, which lingered until its 12h safety timer — holding its port so the next `ok start` bound a different one. The signal path now runs the same guarded UI teardown as idle-shutdown, before destroy releases server.lock. The SIGTERM → grace-poll → SIGKILL escalation is factored out of `buildIdleShutdownHandler` into a shared `teardownUiSibling`, so both call sites honor the SAME ownership guard: `spawnedUiPid` scopes the kill to the sibling this process spawned, leaving a lock holder we did not spawn (a desktop shell, another session) untouched. `BootedStartServer` now exposes `spawnedUiPid` so the signal handler passes the owned pid through; the signal path's teardown is wrapped so a failure there never bypasses destroy(). Reworks community PR inkeep/open-knowledge#702 by @Adityakk9031, preserving the `spawnedUiPid` ownership guard that #702 dropped and fixing its changeset (correct package name, no BOM). Adds `teardownUiSibling` unit tests covering the signal path's guard. Co-authored-by: Aditya kumar singh <143548997+Adityakk9031@users.noreply.github.com> GitOrigin-RevId: 67089dae1d813dbd3470369c7fd73142801a67c7 --- .changeset/stale-ui-shutdown.md | 14 ++ knip.config.ts | 4 +- packages/cli/src/commands/start.test.ts | 73 ++++++++ packages/cli/src/commands/start.ts | 164 +++++++++++------- .../core/src/bridge/bind-frontmatter-doc.ts | 1 - packages/core/src/bridge/bridge-invariant.ts | 1 - .../core/src/bridge/doc-boundary-space.ts | 1 - packages/core/src/bridge/growth-detect.ts | 1 - packages/core/src/bridge/merge-three-way.ts | 1 - packages/core/src/bridge/normalize.ts | 1 - .../src/bridge/pm-structural-equivalence.ts | 3 - packages/core/src/bridge/subsequence.ts | 1 - .../core/src/markdown/callout-transformer.ts | 1 - .../core/src/markdown/comment-promoter.ts | 1 - .../src/markdown/dedent-block-jsx-close.ts | 1 - .../markdown/details-accordion-promoter.ts | 1 - packages/core/src/markdown/fence-regions.ts | 1 - packages/core/src/markdown/fixtures/index.ts | 6 - .../src/markdown/fixtures/perf/generate.ts | 5 - .../src/markdown/guard-flanking-matrix.ts | 1 - .../core/src/markdown/handler-shadow-audit.ts | 1 - .../core/src/markdown/highlight-promoter.ts | 1 - packages/core/src/markdown/html-to-mdast.ts | 4 +- packages/core/src/markdown/image-promoter.ts | 1 - packages/core/src/markdown/index.ts | 9 - .../core/src/markdown/lint/config-files.ts | 1 - .../core/src/markdown/lint/config-schemas.ts | 1 - .../core/src/markdown/lint/default-config.ts | 1 - packages/core/src/markdown/lint/index.ts | 1 - packages/core/src/markdown/lint/plugins.ts | 2 - packages/core/src/markdown/lint/types.ts | 2 - packages/core/src/markdown/math-promoter.ts | 1 - .../src/markdown/mdast-to-hast-handlers.ts | 1 - packages/core/src/markdown/mdast-to-html.ts | 1 - packages/core/src/markdown/merged-walker.ts | 1 - .../core/src/markdown/mermaid-promoter.ts | 1 - .../core/src/markdown/parse-with-fallback.ts | 5 - .../core/src/markdown/parser-drop-closure.ts | 1 - packages/core/src/markdown/pipeline.ts | 1 - .../core/src/markdown/position-aware-join.ts | 1 - packages/core/src/markdown/position-slice.ts | 4 +- .../rehype-plugins/skip-notion-whitespace.ts | 1 - .../rehype-plugins/strip-cocoa-meta.ts | 1 - .../rehype-plugins/strip-gdocs-wrapper.ts | 1 - .../rehype-plugins/strip-github-hovercard.ts | 1 - .../rehype-plugins/strip-gmail-classes.ts | 1 - .../rehype-plugins/strip-gsheets-wrapper.ts | 1 - .../rehype-plugins/strip-mso-styles.ts | 1 - .../rehype-plugins/strip-slack-classes.ts | 1 - .../rehype-plugins/strip-vscode-spans.ts | 1 - .../core/src/markdown/resolve-image-url.ts | 1 - packages/core/src/markdown/safe-url.ts | 1 - .../core/src/markdown/serialize-helpers.ts | 1 - .../markdown/single-dollar-math-promoter.ts | 1 - .../core/src/markdown/to-markdown-handlers.ts | 1 - .../core/src/markdown/unknown-mdast-guard.ts | 1 - .../core/src/markdown/whitespace-char-ref.ts | 1 - .../core/src/markdown/wiki-link-micromark.ts | 3 - 58 files changed, 194 insertions(+), 148 deletions(-) create mode 100644 .changeset/stale-ui-shutdown.md diff --git a/.changeset/stale-ui-shutdown.md b/.changeset/stale-ui-shutdown.md new file mode 100644 index 000000000..27ea3f634 --- /dev/null +++ b/.changeset/stale-ui-shutdown.md @@ -0,0 +1,14 @@ +--- +'@inkeep/open-knowledge': patch +--- + +fix: tear down the `ok ui` sibling when `ok start` exits via a signal + +`ok start` spawns a detached `ok ui` process to serve the editor shell. On +`Ctrl+C` (SIGINT/SIGTERM) the CLI destroyed the collab server but left that UI +child running until its 12-hour safety timer expired, holding its port so the +next `ok start` bound a different one. The signal path now runs the same guarded +UI teardown as idle-shutdown — SIGTERM, wait out a grace window, then escalate +to SIGKILL — scoped by `spawnedUiPid` to the sibling this process actually +spawned, so a lock holder we did not spawn (a desktop shell, another session) is +left alone. diff --git a/knip.config.ts b/knip.config.ts index 6e3847861..296751a02 100644 --- a/knip.config.ts +++ b/knip.config.ts @@ -121,9 +121,7 @@ export default { 'packages/server': { entry: ['src/**/*.test.ts', 'src/parse-worker.ts'], project: 'src/**', - ignoreDependencies: [ - '@types/shell-quote', - ], + ignoreDependencies: ['@types/shell-quote'], }, 'packages/cli': { entry: ['src/**/*.test.ts', 'scripts/*.ts', 'tests/**/*.ts', 'src/parse-worker.ts'], diff --git a/packages/cli/src/commands/start.test.ts b/packages/cli/src/commands/start.test.ts index 0d5c6b501..28ae6961e 100644 --- a/packages/cli/src/commands/start.test.ts +++ b/packages/cli/src/commands/start.test.ts @@ -26,6 +26,7 @@ import { shouldConnectToExistingServer, spawnOkUi, startCommand, + teardownUiSibling, tryDescribeLockCollision, type UiSpawnDecision, withEphemeralTempDirReap, @@ -346,6 +347,78 @@ describe('buildIdleShutdownHandler', () => { }); }); +describe('teardownUiSibling (signal-driven ok start teardown)', () => { + test('SIGTERMs the owned sibling and returns once it exits within grace', async () => { + const events: string[] = []; + let alive = true; + await teardownUiSibling({ + readUiLock: () => ({ pid: 1234, port: 3000 }), + spawnedUiPid: () => 1234, + isAlive: () => alive, + killPid: (pid, sig) => { + events.push(`kill:${pid}:${sig}`); + if (sig === 'SIGTERM') alive = false; + }, + sigtermGraceMs: 100, + sigtermPollIntervalMs: 5, + sleep: async () => {}, + reason: 'shutdown', + }); + expect(events).toEqual(['kill:1234:SIGTERM']); + }); + + test('escalates to SIGKILL when the owned sibling ignores SIGTERM', async () => { + const events: string[] = []; + await teardownUiSibling({ + readUiLock: () => ({ pid: 1234, port: 3000 }), + spawnedUiPid: () => 1234, + isAlive: () => true, + killPid: (pid, sig) => events.push(`kill:${pid}:${sig}`), + sigtermGraceMs: 20, + sigtermPollIntervalMs: 5, + sleep: async () => {}, + reason: 'shutdown', + }); + expect(events).toEqual(['kill:1234:SIGTERM', 'kill:1234:SIGKILL']); + }); + + // The regression guard: on the signal path too, a live ui.lock holder that is + // NOT the pid we spawned must be left untouched. This is the exact incident + // (a stale/idle server SIGTERMing a live desktop-spawned or other-session + // server that merely holds ui.lock) the spawnedUiPid guard exists to prevent. + test('leaves a live ui.lock holder alone when it is not the spawned sibling', async () => { + const events: string[] = []; + const infos: object[] = []; + await teardownUiSibling({ + readUiLock: () => ({ pid: 9999, port: 3000 }), + spawnedUiPid: () => 1234, + isAlive: () => true, + killPid: (pid, sig) => events.push(`kill:${pid}:${sig}`), + log: { info: (obj) => infos.push(obj), warn: () => {}, error: () => {} }, + reason: 'shutdown', + }); + expect(events).toEqual([]); + expect(infos.find((entry) => (entry as { pid?: number }).pid === 9999)).toBeDefined(); + }); + + // Defense-in-depth: even if a null ownPid reaches teardownUiSibling (the + // signal handler's `!== null` guard means it normally can't), a live lock + // holder routes through the ownership-guard "leave it alone" branch — a null + // ownPid never equals a real pid — so nothing is killed. + test('leaves a live lock holder alone when ownPid is null (routes through the ownership guard)', async () => { + const events: string[] = []; + await teardownUiSibling({ + readUiLock: () => ({ pid: 52425, port: 64430 }), + spawnedUiPid: () => null, + isAlive: () => true, + killPid: (pid, sig) => events.push(`kill:${pid}:${sig}`), + log: { info: () => {}, warn: () => {}, error: () => {} }, + reason: 'shutdown', + }); + expect(events).toEqual([]); + }); +}); + describe('spawnOkUi', () => { let tmpDir: string; let lockDir: string; diff --git a/packages/cli/src/commands/start.ts b/packages/cli/src/commands/start.ts index 9ac635b57..0693c29f2 100644 --- a/packages/cli/src/commands/start.ts +++ b/packages/cli/src/commands/start.ts @@ -426,24 +426,6 @@ interface BuildIdleShutdownHandlerInput { const DEFAULT_SIGTERM_GRACE_MS = SHARED_DEFAULT_SIGTERM_GRACE_MS; const DEFAULT_SIGTERM_POLL_MS = SHARED_DEFAULT_SIGTERM_POLL_MS; -/** - * Build the idle-shutdown `onShutdown` closure. On fire: - * (1) look up `ui.lock`; SIGTERM the sibling if it's still alive AND it is - * the pid this process spawned (`spawnedUiPid`) — a live holder we did - * not spawn is left alone (see the field's docstring for the incident - * class this prevents); - * (2) poll its liveness up to `sigtermGraceMs` (default 10s); - * (3) if still alive after the grace window, escalate to SIGKILL; - * (4) await `destroy()`, which releases `server.lock` as its final step. - * - * Escalation matters because a hung `ok ui` (stuck in a GC pause or a - * downstream fetch in `/api/config`) would otherwise block idle-shutdown - * indefinitely. Escalation is logged at WARN so the operator sees that a - * non-standard path ran. - * - * Extracted so tests can exercise each branch (no UI, live UI, stale UI, - * SIGTERM-takes, SIGKILL-escalation) without standing up Hocuspocus. - */ /** * Wrap an idle-shutdown handler so that, after the server is destroyed, the * ephemeral session's throwaway temp projectDir is removed. Without this an @@ -555,56 +537,81 @@ export function withIdleShutdownProcessExit( }; } -export function buildIdleShutdownHandler( - input: BuildIdleShutdownHandlerInput, -): () => Promise { +/** + * Signal the auto-spawned `ok ui` sibling to exit: SIGTERM, poll its liveness + * up to `sigtermGraceMs`, then escalate to SIGKILL if it's wedged. Shared by + * idle-shutdown and the signal-driven `ok start` teardown so BOTH honor the + * same ownership guard — a live lock holder whose pid is not `spawnedUiPid()` + * is left alone (see that field's docstring for the incident class this + * prevents). `reason` prefixes the log lines to identify the calling path + * (`idle-shutdown` | `shutdown`; defaults to `teardown` when omitted). + * Best-effort throughout: a failed lookup/kill is logged, never thrown, so the + * caller's own teardown (destroy / server.lock release) always proceeds. + */ +export async function teardownUiSibling( + input: Omit & { reason?: string }, +): Promise { const graceMs = input.sigtermGraceMs ?? DEFAULT_SIGTERM_GRACE_MS; const pollMs = input.sigtermPollIntervalMs ?? DEFAULT_SIGTERM_POLL_MS; const sleep = input.sleep ?? ((ms: number) => wait(ms)); + const reason = input.reason ?? 'teardown'; - return async () => { - try { - const lock = input.readUiLock(); - const ownPid = input.spawnedUiPid(); - if (lock && input.isAlive(lock.pid) && lock.pid !== ownPid) { - // The lock holder is alive but is NOT the sibling we spawned — a - // desktop-spawned server advertising its shell, or another session's - // UI. It is not ours to kill; it has its own lifecycle (idle-shutdown - // or the `ok ui` 12h safety net). - input.log?.info( - { pid: lock.pid, port: lock.port, spawnedUiPid: ownPid }, - 'idle-shutdown: ui.lock holder is not our spawned sibling — leaving it alone', - ); - } else if (lock && input.isAlive(lock.pid)) { - try { - input.killPid(lock.pid, 'SIGTERM'); - input.log?.info({ pid: lock.pid, port: lock.port }, 'idle-shutdown: SIGTERM UI sibling'); - // Wait up to graceMs for the UI process to exit under SIGTERM. - const deadline = Date.now() + graceMs; - while (Date.now() < deadline) { - if (!input.isAlive(lock.pid)) break; - await sleep(pollMs); - } - if (input.isAlive(lock.pid)) { - // Grace expired — escalate to SIGKILL. Operators see this at WARN. - try { - input.killPid(lock.pid, 'SIGKILL'); - input.log?.warn( - { pid: lock.pid, graceMs }, - 'idle-shutdown: SIGTERM grace expired — escalated to SIGKILL', - ); - } catch (err) { - input.log?.error({ pid: lock.pid, err }, 'idle-shutdown: SIGKILL failed'); - } + try { + const lock = input.readUiLock(); + const ownPid = input.spawnedUiPid(); + if (lock && input.isAlive(lock.pid) && lock.pid !== ownPid) { + // The lock holder is alive but is NOT the sibling we spawned — a + // desktop-spawned server advertising its shell, or another session's + // UI. It is not ours to kill; it has its own lifecycle (idle-shutdown + // or the `ok ui` 12h safety net). + input.log?.info( + { pid: lock.pid, port: lock.port, spawnedUiPid: ownPid }, + `${reason}: ui.lock holder is not our spawned sibling — leaving it alone`, + ); + } else if (lock && input.isAlive(lock.pid)) { + try { + input.killPid(lock.pid, 'SIGTERM'); + input.log?.info({ pid: lock.pid, port: lock.port }, `${reason}: SIGTERM UI sibling`); + // Wait up to graceMs for the UI process to exit under SIGTERM. + const deadline = Date.now() + graceMs; + while (Date.now() < deadline) { + if (!input.isAlive(lock.pid)) break; + await sleep(pollMs); + } + if (input.isAlive(lock.pid)) { + // Grace expired — escalate to SIGKILL. Operators see this at WARN. + try { + input.killPid(lock.pid, 'SIGKILL'); + input.log?.warn( + { pid: lock.pid, graceMs }, + `${reason}: SIGTERM grace expired — escalated to SIGKILL`, + ); + } catch (err) { + input.log?.error({ pid: lock.pid, err }, `${reason}: SIGKILL failed`); } - } catch (err) { - input.log?.warn({ pid: lock.pid, err }, 'idle-shutdown: failed to SIGTERM UI sibling'); } + } catch (err) { + input.log?.warn({ pid: lock.pid, err }, `${reason}: failed to SIGTERM UI sibling`); } - } catch (err) { - input.log?.warn({ err }, 'idle-shutdown: UI lookup failed; proceeding with destroy'); } - await input.destroy(); + } catch (err) { + input.log?.warn({ err }, `${reason}: UI lookup failed; proceeding`); + } +} + +/** + * The idle-shutdown `onShutdown` closure: tear down the UI sibling (guarded by + * `spawnedUiPid`), then `destroy()` — which releases `server.lock` as its final + * step. A thin adapter over `teardownUiSibling`; the escalation logic lives + * there. + */ +export function buildIdleShutdownHandler( + input: BuildIdleShutdownHandlerInput, +): () => Promise { + const { destroy, ...teardown } = input; + return async () => { + await teardownUiSibling({ ...teardown, reason: 'idle-shutdown' }); + await destroy(); }; } @@ -768,6 +775,14 @@ export interface BootedStartServer { degraded: readonly string[]; /** What we decided about the UI sibling at boot — for tests + status output. */ uiSpawnDecision: UiSpawnDecision; + /** + * Pid of the `ok ui` child THIS process spawned, or null when none was + * spawned (sibling reused, auto-spawn skipped, or desktop single-origin). + * The signal-driven teardown in `runStartCommand` passes this to + * `teardownUiSibling` so it only signals the sibling we own — the same + * ownership guard the idle-shutdown closure applies via `spawnedUiPid`. + */ + spawnedUiPid: number | null; /** * The port `ok ui` is actually serving on, resolved end-to-end: * - `action: 'skip'` (sibling already alive) → `uiSpawnDecision.port` @@ -1126,6 +1141,7 @@ export async function bootStartServer(opts: BootStartServerOptions): Promise readUiLock(booted.lockDir), + spawnedUiPid: () => booted.spawnedUiPid, + isAlive: isProcessAlive, + killPid: (pid, sig) => process.kill(pid, sig), + log: getLogger('start'), + reason: 'shutdown', + }); + } catch (err) { + console.error( + `${error('ui sibling teardown failed:')} ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`, + ); + } + } try { await booted.destroy(); } catch (err) { diff --git a/packages/core/src/bridge/bind-frontmatter-doc.ts b/packages/core/src/bridge/bind-frontmatter-doc.ts index 8f9d95287..c014b1413 100644 --- a/packages/core/src/bridge/bind-frontmatter-doc.ts +++ b/packages/core/src/bridge/bind-frontmatter-doc.ts @@ -1,4 +1,3 @@ - import type * as Y from 'yjs'; import type { Err, Ok, Result } from '../config/result.ts'; import { type FrontmatterValidationError, toFrontmatterIssue } from '../frontmatter/errors.ts'; diff --git a/packages/core/src/bridge/bridge-invariant.ts b/packages/core/src/bridge/bridge-invariant.ts index 8bfdb40cd..005627195 100644 --- a/packages/core/src/bridge/bridge-invariant.ts +++ b/packages/core/src/bridge/bridge-invariant.ts @@ -1,4 +1,3 @@ - import { fnv1aDigest } from './hash-util.ts'; export type BridgeInvariantSite = 'observer-b' | 'persistence' | 'test-harness'; diff --git a/packages/core/src/bridge/doc-boundary-space.ts b/packages/core/src/bridge/doc-boundary-space.ts index c18c086d5..8890f6739 100644 --- a/packages/core/src/bridge/doc-boundary-space.ts +++ b/packages/core/src/bridge/doc-boundary-space.ts @@ -1,4 +1,3 @@ - import { FRONTMATTER_RE, stripFrontmatter } from '../extensions/frontmatter.ts'; const LEADING_BOUNDARY_RE = /^(?:\r?\n)+/; diff --git a/packages/core/src/bridge/growth-detect.ts b/packages/core/src/bridge/growth-detect.ts index 463e89595..9d9c244b0 100644 --- a/packages/core/src/bridge/growth-detect.ts +++ b/packages/core/src/bridge/growth-detect.ts @@ -1,4 +1,3 @@ - const DEFAULT_MIN_SUBSTANTIVE_LINE_LENGTH = 16; export const DUPLICATION_GATE_MIN_LINE_LENGTH = 8; diff --git a/packages/core/src/bridge/merge-three-way.ts b/packages/core/src/bridge/merge-three-way.ts index 3c927eb47..3ab9cc6ef 100644 --- a/packages/core/src/bridge/merge-three-way.ts +++ b/packages/core/src/bridge/merge-three-way.ts @@ -38,7 +38,6 @@ function mergeThreeWayImpl(baseline: string, userText: string, agentText: string return parts.join('\n'); } - export type BridgeMergeContentLossSide = 'user' | 'agent'; export type BridgeMergeContentLossWhich = 'substring' | 'order' | 'growth'; diff --git a/packages/core/src/bridge/normalize.ts b/packages/core/src/bridge/normalize.ts index 3f0880aa5..4b8890476 100644 --- a/packages/core/src/bridge/normalize.ts +++ b/packages/core/src/bridge/normalize.ts @@ -1,4 +1,3 @@ - const COMMONMARK_ESCAPE_RE = /\\([!"#$%&'()*+,\-./:;<=>?@[\]^_`{|}~])/g; const TABLE_ALIGN_ROW_RE = /^\s*\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)+\|?\s*$/; diff --git a/packages/core/src/bridge/pm-structural-equivalence.ts b/packages/core/src/bridge/pm-structural-equivalence.ts index 0be5203e4..754dc5cc2 100644 --- a/packages/core/src/bridge/pm-structural-equivalence.ts +++ b/packages/core/src/bridge/pm-structural-equivalence.ts @@ -32,7 +32,6 @@ const BR_SENTINEL: PmStructuralNode = { type: '__br__' }; const BR_LITERAL_RE = /^$/i; - interface DegradeEntry { readonly label: string; readonly severity: ToleranceClassSeverity; @@ -128,7 +127,6 @@ export interface ComparePmStructuralOptions { ignoreAttrs?: (attrKey: string) => boolean; } - /** Rebuild a node with `fn` applied to each child; identity when childless. * Shared by the degrade canonicalizers so none re-implements the walk. */ function mapChildren( @@ -250,7 +248,6 @@ function applyDegrades(tree: PmStructuralNode): { return { normalized: current, fired }; } - export function comparePmStructural( expected: PmStructuralNode, actual: PmStructuralNode, diff --git a/packages/core/src/bridge/subsequence.ts b/packages/core/src/bridge/subsequence.ts index dca644141..af20de1e2 100644 --- a/packages/core/src/bridge/subsequence.ts +++ b/packages/core/src/bridge/subsequence.ts @@ -1,4 +1,3 @@ - /** True when `needle` is a subsequence of `haystack` (two-pointer, O(n+m)): * insertions in `haystack` are free; any dropped or substituted `needle` byte * fails. */ diff --git a/packages/core/src/markdown/callout-transformer.ts b/packages/core/src/markdown/callout-transformer.ts index a363dfd54..83d47404a 100644 --- a/packages/core/src/markdown/callout-transformer.ts +++ b/packages/core/src/markdown/callout-transformer.ts @@ -1,4 +1,3 @@ - import type { Blockquote, Paragraph, Root } from 'mdast'; import type { MdxJsxAttribute, MdxJsxFlowElement } from 'mdast-util-mdx'; import { visit } from 'unist-util-visit'; diff --git a/packages/core/src/markdown/comment-promoter.ts b/packages/core/src/markdown/comment-promoter.ts index 47f0d07e4..193e286b0 100644 --- a/packages/core/src/markdown/comment-promoter.ts +++ b/packages/core/src/markdown/comment-promoter.ts @@ -1,4 +1,3 @@ - import type { Nodes, Paragraph, PhrasingContent, Root, RootContent, Text } from 'mdast'; import { SKIP, visit } from 'unist-util-visit'; import type { VFile } from 'vfile'; diff --git a/packages/core/src/markdown/dedent-block-jsx-close.ts b/packages/core/src/markdown/dedent-block-jsx-close.ts index b6299a948..ece5c02a1 100644 --- a/packages/core/src/markdown/dedent-block-jsx-close.ts +++ b/packages/core/src/markdown/dedent-block-jsx-close.ts @@ -1,4 +1,3 @@ - import { findFencedRegions, isInsideFence } from './fence-regions.ts'; const INDENTED_BLOCK_JSX_CLOSE_RE = /^([ ]{1,3})(<\/[A-Z][A-Za-z0-9_]*\s*>)([ \t]*)$/gm; diff --git a/packages/core/src/markdown/details-accordion-promoter.ts b/packages/core/src/markdown/details-accordion-promoter.ts index acd398aa6..54774764a 100644 --- a/packages/core/src/markdown/details-accordion-promoter.ts +++ b/packages/core/src/markdown/details-accordion-promoter.ts @@ -1,4 +1,3 @@ - import type { Nodes, Paragraph, Parent, Root, Text } from 'mdast'; import type { MdxJsxAttribute, MdxJsxFlowElement } from 'mdast-util-mdx'; import { visit } from 'unist-util-visit'; diff --git a/packages/core/src/markdown/fence-regions.ts b/packages/core/src/markdown/fence-regions.ts index fb5ac8e8a..c7518d614 100644 --- a/packages/core/src/markdown/fence-regions.ts +++ b/packages/core/src/markdown/fence-regions.ts @@ -1,4 +1,3 @@ - const FENCE_RE = /^(`{3,}|~{3,})/gm; export function findFencedRegions(src: string): Array<[number, number]> { diff --git a/packages/core/src/markdown/fixtures/index.ts b/packages/core/src/markdown/fixtures/index.ts index 6931623c7..00f5771ee 100644 --- a/packages/core/src/markdown/fixtures/index.ts +++ b/packages/core/src/markdown/fixtures/index.ts @@ -1,4 +1,3 @@ - import { readFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -9,7 +8,6 @@ function fixturePath(...segments: string[]): string { return resolve(FIXTURES_DIR, ...segments); } - interface GfmExample { section: string; markdown: string; @@ -19,7 +17,6 @@ export function loadGfmExamples(): GfmExample[] { return JSON.parse(readFileSync(fixturePath('gfm', 'examples.json'), 'utf8')) as GfmExample[]; } - interface MdxCrashEntry { id: string; input: string; @@ -71,7 +68,6 @@ export function loadLargeEmbedFixtures(): LargeEmbedFixture[] { ) as LargeEmbedFixture[]; } - export function loadPrd6955Before(): string { return readFileSync(fixturePath('regression', 'prd-6955-before.md'), 'utf8'); } @@ -80,7 +76,6 @@ export function loadPrd6955CorruptedTriplicated(): string { return readFileSync(fixturePath('regression', 'prd-6955-corrupted-triplicated.md'), 'utf8'); } - export interface NgPinnedCase { id: string; name: string; @@ -97,7 +92,6 @@ export function loadNgPinnedCases(): NgPinnedCase[] { ) as NgPinnedCase[]; } - export function loadLargeRealistic(): string { return readFileSync(fixturePath('perf', 'large-realistic.md'), 'utf8'); } diff --git a/packages/core/src/markdown/fixtures/perf/generate.ts b/packages/core/src/markdown/fixtures/perf/generate.ts index daa3ef82b..8b1eb8519 100644 --- a/packages/core/src/markdown/fixtures/perf/generate.ts +++ b/packages/core/src/markdown/fixtures/perf/generate.ts @@ -1,4 +1,3 @@ - import { writeFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -11,7 +10,6 @@ const BASELINE_ONLY_COUNTS = [500, 2500] as const; const SEED = 0xf1de1117; - function mulberry32(seed: number): () => number { let a = seed >>> 0; return () => { @@ -38,7 +36,6 @@ function pickWeighted(rand: () => number, items: readonly [T, number][]): T { return items[items.length - 1][0]; } - const WORDS = [ 'lorem', 'ipsum', @@ -156,7 +153,6 @@ function mdxBlock(rand: () => number): string { return `<${name}>\n\n${sentence(rand)}\n\n`; } - type BlockKind = 'paragraph' | 'heading' | 'list' | 'code' | 'table' | 'mdx'; const MIX: readonly [BlockKind, number][] = [ @@ -195,7 +191,6 @@ function generateDocument(blockCount: number, seed: number): string { return `${blocks.join('\n\n')}\n`; } - function main(): void { for (const count of [...BLOCK_COUNTS, ...BASELINE_ONLY_COUNTS]) { const doc = generateDocument(count, SEED); diff --git a/packages/core/src/markdown/guard-flanking-matrix.ts b/packages/core/src/markdown/guard-flanking-matrix.ts index 32c055c5b..420dc0fe5 100644 --- a/packages/core/src/markdown/guard-flanking-matrix.ts +++ b/packages/core/src/markdown/guard-flanking-matrix.ts @@ -7,7 +7,6 @@ import { import { BACKSLASH_GUARD_SUBSTITUTIONS, encodeBackslashEscapes } from './backslash-escape-guard.ts'; import { ENTITY_REF_GUARD_SUBSTITUTIONS, encodeEntityRefs } from './entity-ref-guard.ts'; - export const ATTENTION_DELIMITERS = ['*', '_', '**', '~~', '=='] as const; export type FlankClass = 'whitespace' | 'punctuation' | 'other'; diff --git a/packages/core/src/markdown/handler-shadow-audit.ts b/packages/core/src/markdown/handler-shadow-audit.ts index 283913255..2653473da 100644 --- a/packages/core/src/markdown/handler-shadow-audit.ts +++ b/packages/core/src/markdown/handler-shadow-audit.ts @@ -1,4 +1,3 @@ - export interface HandlerShadowWitness { input: string; expect: 'byte' | 'byte-and-reparse-type'; diff --git a/packages/core/src/markdown/highlight-promoter.ts b/packages/core/src/markdown/highlight-promoter.ts index 6319f264f..f55912407 100644 --- a/packages/core/src/markdown/highlight-promoter.ts +++ b/packages/core/src/markdown/highlight-promoter.ts @@ -1,4 +1,3 @@ - import type { Nodes, Parent, PhrasingContent, Root, Text } from 'mdast'; import type { MdxJsxTextElement } from 'mdast-util-mdx'; import { SKIP, visit } from 'unist-util-visit'; diff --git a/packages/core/src/markdown/html-to-mdast.ts b/packages/core/src/markdown/html-to-mdast.ts index 8dc3a81b5..b1f252c31 100644 --- a/packages/core/src/markdown/html-to-mdast.ts +++ b/packages/core/src/markdown/html-to-mdast.ts @@ -1,4 +1,3 @@ - import type { Root as HastRoot } from 'hast'; import type { List, Root as MdastRoot } from 'mdast'; import rehypeParse from 'rehype-parse'; @@ -86,8 +85,7 @@ export function htmlToMdast(html: string, options?: HtmlToMdastOptions): MdastRo throw new HtmlPayloadTooLargeError(html.length, maxBytes); } - const processor = unified() - .use(rehypeParse, { fragment: true }); + const processor = unified().use(rehypeParse, { fragment: true }); for (const plugin of cleanupPlugins) { processor.use(plugin); diff --git a/packages/core/src/markdown/image-promoter.ts b/packages/core/src/markdown/image-promoter.ts index 994eb6917..706fa9afb 100644 --- a/packages/core/src/markdown/image-promoter.ts +++ b/packages/core/src/markdown/image-promoter.ts @@ -1,4 +1,3 @@ - import type { Image, Paragraph, Root } from 'mdast'; import type { MdxJsxAttribute, MdxJsxFlowElement } from 'mdast-util-mdx'; import { visit } from 'unist-util-visit'; diff --git a/packages/core/src/markdown/index.ts b/packages/core/src/markdown/index.ts index fc30bda02..8b36807fd 100644 --- a/packages/core/src/markdown/index.ts +++ b/packages/core/src/markdown/index.ts @@ -1,4 +1,3 @@ - import { type FromProseMirrorOptions, fromPmMark, @@ -197,7 +196,6 @@ export class MarkdownManager { } } - const registry = createRegistry(); function destructureAttrs( @@ -246,7 +244,6 @@ function destructureAttrs( return result; } - function hasDirtyDescendant(node: PmNode): boolean { let found = false; node.descendants((child) => { @@ -266,7 +263,6 @@ function effectiveDirty(node: PmNode, freshnessChecker?: StructuralFreshnessChec return freshnessChecker ? freshnessChecker.isDiverged(node.toJSON()) : false; } - function isEmptyMdastParagraph(node: MdastNodes): boolean { if (node.type !== 'paragraph') return false; const children = node.children ?? []; @@ -335,7 +331,6 @@ function extractTextFromMdastNodes(nodes: MdastNodes[]): string { return out; } - import { AUDIO_EXTENSIONS, FILE_ATTACHMENT_EXTENSIONS, @@ -582,7 +577,6 @@ function buildMdastToPmHandlers( })); } - if (m.emphasis) { handlers.emphasis = toPmMark(m.emphasis, (node: Emphasis) => ({ sourceDelimiter: node.data?.sourceDelimiter ?? '*', @@ -663,7 +657,6 @@ function buildMdastToPmHandlers( })); } - if (m.link) { const sourceLiteralMark = m.sourceLiteral; handlers.link = (node: Link, _parent: MdastParent, state: MdastToPmState) => { @@ -942,7 +935,6 @@ function buildMdastToPmHandlers( }; } - const blockUnknownHandler = (node: { type: string; position?: { start: { offset: number }; end: { offset: number } }; @@ -1068,7 +1060,6 @@ function buildMdastToPmHandlers( return handlers as RemarkProseMirrorOptions['handlers']; } - function buildPmToMdastHandlers( schema: Schema, freshness: FreshnessCheckerHolder, diff --git a/packages/core/src/markdown/lint/config-files.ts b/packages/core/src/markdown/lint/config-files.ts index 94b34dee1..312c9c74b 100644 --- a/packages/core/src/markdown/lint/config-files.ts +++ b/packages/core/src/markdown/lint/config-files.ts @@ -1,4 +1,3 @@ - const MARKDOWNLINT_JSON_CONFIG_FILES: ReadonlySet = new Set([ '.markdownlint.json', '.markdownlint.jsonc', diff --git a/packages/core/src/markdown/lint/config-schemas.ts b/packages/core/src/markdown/lint/config-schemas.ts index 207cd95c2..e78a1381d 100644 --- a/packages/core/src/markdown/lint/config-schemas.ts +++ b/packages/core/src/markdown/lint/config-schemas.ts @@ -29,7 +29,6 @@ const fullPluginShape = Object.fromEntries( LINT_PLUGINS.map((plugin) => [plugin.id, plugin.sliceSchema]), ) as z.ZodRawShape; - export const LinterConfigSchema = z.object({ enabled: z.boolean(), plugins: z.object(fullPluginShape), diff --git a/packages/core/src/markdown/lint/default-config.ts b/packages/core/src/markdown/lint/default-config.ts index a51f2ffb0..d48eea5e4 100644 --- a/packages/core/src/markdown/lint/default-config.ts +++ b/packages/core/src/markdown/lint/default-config.ts @@ -6,7 +6,6 @@ export const DEFAULT_MARKDOWNLINT_CONFIG: Record | undefined, ): Configuration { diff --git a/packages/core/src/markdown/lint/index.ts b/packages/core/src/markdown/lint/index.ts index d763d8d28..8a154ce30 100644 --- a/packages/core/src/markdown/lint/index.ts +++ b/packages/core/src/markdown/lint/index.ts @@ -1,4 +1,3 @@ - import { LINT_PLUGINS, type LinterConfig } from './plugins.ts'; import type { LintDiagnostic } from './types.ts'; diff --git a/packages/core/src/markdown/lint/plugins.ts b/packages/core/src/markdown/lint/plugins.ts index 65a1b564c..1cd40ef3a 100644 --- a/packages/core/src/markdown/lint/plugins.ts +++ b/packages/core/src/markdown/lint/plugins.ts @@ -1,4 +1,3 @@ - import { z } from 'zod'; import { DEFAULT_MARKDOWNLINT_CONFIG, resolveMarkdownlintConfig } from './default-config.ts'; import { fixMarkdownText, runMarkdownlint } from './markdownlint-runner.ts'; @@ -9,7 +8,6 @@ import { type MarkdownlintSlice, } from './types.ts'; - const MarkdownlintRuleSettingSchema = z.union([ z.boolean(), z.enum(MARKDOWNLINT_RULE_SEVERITIES), diff --git a/packages/core/src/markdown/lint/types.ts b/packages/core/src/markdown/lint/types.ts index 3651bd7eb..617f6e5b9 100644 --- a/packages/core/src/markdown/lint/types.ts +++ b/packages/core/src/markdown/lint/types.ts @@ -1,4 +1,3 @@ - export const LINT_PLUGIN_IDS = ['markdownlint'] as const; export type LintPluginId = (typeof LINT_PLUGIN_IDS)[number]; @@ -42,7 +41,6 @@ export interface MarkdownlintSlice { rules: Record; } - interface RuleOptionSpecBase { key: string; description: string; diff --git a/packages/core/src/markdown/math-promoter.ts b/packages/core/src/markdown/math-promoter.ts index bd79424c7..a5b511f25 100644 --- a/packages/core/src/markdown/math-promoter.ts +++ b/packages/core/src/markdown/math-promoter.ts @@ -1,4 +1,3 @@ - import type { Code, Root } from 'mdast'; import type { Math as MdastMath } from 'mdast-util-math'; import type { MdxJsxAttribute, MdxJsxFlowElement } from 'mdast-util-mdx'; diff --git a/packages/core/src/markdown/mdast-to-hast-handlers.ts b/packages/core/src/markdown/mdast-to-hast-handlers.ts index f84967798..679c72ef7 100644 --- a/packages/core/src/markdown/mdast-to-hast-handlers.ts +++ b/packages/core/src/markdown/mdast-to-hast-handlers.ts @@ -1,4 +1,3 @@ - import type { Comment, Element, ElementContent, Properties } from 'hast'; import type { FootnoteDefinition, FootnoteReference } from 'mdast'; import type { MdxJsxFlowElement, MdxJsxTextElement } from 'mdast-util-mdx'; diff --git a/packages/core/src/markdown/mdast-to-html.ts b/packages/core/src/markdown/mdast-to-html.ts index df788a80d..8ce2b2492 100644 --- a/packages/core/src/markdown/mdast-to-html.ts +++ b/packages/core/src/markdown/mdast-to-html.ts @@ -1,4 +1,3 @@ - import type { Element, Root as HastRoot, Text as HastText } from 'hast'; import type { Root as MdastRoot, Text as MdastText } from 'mdast'; import type { Handler } from 'mdast-util-to-hast'; diff --git a/packages/core/src/markdown/merged-walker.ts b/packages/core/src/markdown/merged-walker.ts index fa3cfcd86..bfd7e3109 100644 --- a/packages/core/src/markdown/merged-walker.ts +++ b/packages/core/src/markdown/merged-walker.ts @@ -1,4 +1,3 @@ - import type { Nodes, Parent, Root } from 'mdast'; import { SKIP, visit } from 'unist-util-visit'; import type { VFile } from 'vfile'; diff --git a/packages/core/src/markdown/mermaid-promoter.ts b/packages/core/src/markdown/mermaid-promoter.ts index 17ed2b829..1a897fca8 100644 --- a/packages/core/src/markdown/mermaid-promoter.ts +++ b/packages/core/src/markdown/mermaid-promoter.ts @@ -1,4 +1,3 @@ - import type { Code, Root } from 'mdast'; import type { MdxJsxAttribute, MdxJsxFlowElement } from 'mdast-util-mdx'; import { visit } from 'unist-util-visit'; diff --git a/packages/core/src/markdown/parse-with-fallback.ts b/packages/core/src/markdown/parse-with-fallback.ts index 7e4a9a48b..9864b3b60 100644 --- a/packages/core/src/markdown/parse-with-fallback.ts +++ b/packages/core/src/markdown/parse-with-fallback.ts @@ -168,7 +168,6 @@ export function parseRecursive( } } - interface VFilePlace { offset?: number; start?: { offset?: number }; @@ -187,7 +186,6 @@ function extractErrorOffset(err: unknown): number | undefined { return undefined; } - interface Region { start: number; end: number; @@ -211,7 +209,6 @@ function nearestBlankLineAfter(src: string, offset: number): number | null { return null; } - export interface TagEvent { kind: 'open' | 'close' | 'self-close'; name: string; @@ -358,7 +355,6 @@ function findFallbackRegion(src: string, errorOffset: number): Region { return { start: blockStart, end: blockEnd }; } - interface SourceBlock { src: string; start: number; @@ -456,7 +452,6 @@ function tryPerBlockFallback( }; } - function wholeDocRawText(source: string): JSONContent { return { type: 'doc', diff --git a/packages/core/src/markdown/parser-drop-closure.ts b/packages/core/src/markdown/parser-drop-closure.ts index 43d420187..e6ab4fd57 100644 --- a/packages/core/src/markdown/parser-drop-closure.ts +++ b/packages/core/src/markdown/parser-drop-closure.ts @@ -1,4 +1,3 @@ - export type DroppedTokenAdjudication = | { kind: 'format-dof-axis'; diff --git a/packages/core/src/markdown/pipeline.ts b/packages/core/src/markdown/pipeline.ts index e9e507469..7f2d5adca 100644 --- a/packages/core/src/markdown/pipeline.ts +++ b/packages/core/src/markdown/pipeline.ts @@ -1,4 +1,3 @@ - import { type FromProseMirrorOptions, fromProseMirror, diff --git a/packages/core/src/markdown/position-aware-join.ts b/packages/core/src/markdown/position-aware-join.ts index f8f5e8530..5a5e4c42a 100644 --- a/packages/core/src/markdown/position-aware-join.ts +++ b/packages/core/src/markdown/position-aware-join.ts @@ -1,4 +1,3 @@ - import type { Join } from 'mdast-util-to-markdown'; import type { Position } from 'unist'; diff --git a/packages/core/src/markdown/position-slice.ts b/packages/core/src/markdown/position-slice.ts index e5b4c9eba..0f399d34c 100644 --- a/packages/core/src/markdown/position-slice.ts +++ b/packages/core/src/markdown/position-slice.ts @@ -1,4 +1,3 @@ - import type { Nodes, Root } from 'mdast'; import { visit } from 'unist-util-visit'; import type { VFile } from 'vfile'; @@ -477,8 +476,7 @@ export function applyPositionSliceToNode( const first = source[startOff]; if (first !== '[' && first !== '<' && !node.title) { node.data.sourceStyle = 'gfm-autolink'; - } - else if (first === '[') { + } else if (first === '[') { const slice = source.slice(startOff, endOff); const closeBracketIdx = slice.lastIndexOf(']('); if (closeBracketIdx !== -1) { diff --git a/packages/core/src/markdown/rehype-plugins/skip-notion-whitespace.ts b/packages/core/src/markdown/rehype-plugins/skip-notion-whitespace.ts index ca225ce9b..db10fa54b 100644 --- a/packages/core/src/markdown/rehype-plugins/skip-notion-whitespace.ts +++ b/packages/core/src/markdown/rehype-plugins/skip-notion-whitespace.ts @@ -1,4 +1,3 @@ - import type { Comment, Element, Root, Text } from 'hast'; import type { Plugin } from 'unified'; diff --git a/packages/core/src/markdown/rehype-plugins/strip-cocoa-meta.ts b/packages/core/src/markdown/rehype-plugins/strip-cocoa-meta.ts index 3a2524a9a..34068d103 100644 --- a/packages/core/src/markdown/rehype-plugins/strip-cocoa-meta.ts +++ b/packages/core/src/markdown/rehype-plugins/strip-cocoa-meta.ts @@ -1,4 +1,3 @@ - import type { Element, ElementContent, Root } from 'hast'; import type { Plugin } from 'unified'; diff --git a/packages/core/src/markdown/rehype-plugins/strip-gdocs-wrapper.ts b/packages/core/src/markdown/rehype-plugins/strip-gdocs-wrapper.ts index 2279a0189..93151d43b 100644 --- a/packages/core/src/markdown/rehype-plugins/strip-gdocs-wrapper.ts +++ b/packages/core/src/markdown/rehype-plugins/strip-gdocs-wrapper.ts @@ -1,4 +1,3 @@ - import type { Element, Root } from 'hast'; import type { Plugin } from 'unified'; diff --git a/packages/core/src/markdown/rehype-plugins/strip-github-hovercard.ts b/packages/core/src/markdown/rehype-plugins/strip-github-hovercard.ts index a0f467c42..188f02d83 100644 --- a/packages/core/src/markdown/rehype-plugins/strip-github-hovercard.ts +++ b/packages/core/src/markdown/rehype-plugins/strip-github-hovercard.ts @@ -1,4 +1,3 @@ - import type { Element, Root } from 'hast'; import type { Plugin } from 'unified'; diff --git a/packages/core/src/markdown/rehype-plugins/strip-gmail-classes.ts b/packages/core/src/markdown/rehype-plugins/strip-gmail-classes.ts index 4e8542a21..4ba2c3418 100644 --- a/packages/core/src/markdown/rehype-plugins/strip-gmail-classes.ts +++ b/packages/core/src/markdown/rehype-plugins/strip-gmail-classes.ts @@ -1,4 +1,3 @@ - import type { Element, Root } from 'hast'; import type { Plugin } from 'unified'; diff --git a/packages/core/src/markdown/rehype-plugins/strip-gsheets-wrapper.ts b/packages/core/src/markdown/rehype-plugins/strip-gsheets-wrapper.ts index a3c96b24a..1a74b1272 100644 --- a/packages/core/src/markdown/rehype-plugins/strip-gsheets-wrapper.ts +++ b/packages/core/src/markdown/rehype-plugins/strip-gsheets-wrapper.ts @@ -1,4 +1,3 @@ - import type { Element, ElementContent, Root } from 'hast'; import type { Plugin } from 'unified'; diff --git a/packages/core/src/markdown/rehype-plugins/strip-mso-styles.ts b/packages/core/src/markdown/rehype-plugins/strip-mso-styles.ts index e95cf43d6..de35e7b79 100644 --- a/packages/core/src/markdown/rehype-plugins/strip-mso-styles.ts +++ b/packages/core/src/markdown/rehype-plugins/strip-mso-styles.ts @@ -1,4 +1,3 @@ - import type { Comment, Element, ElementContent, Root } from 'hast'; import type { Plugin } from 'unified'; diff --git a/packages/core/src/markdown/rehype-plugins/strip-slack-classes.ts b/packages/core/src/markdown/rehype-plugins/strip-slack-classes.ts index ffdb4d5b5..b4be1b2ea 100644 --- a/packages/core/src/markdown/rehype-plugins/strip-slack-classes.ts +++ b/packages/core/src/markdown/rehype-plugins/strip-slack-classes.ts @@ -1,4 +1,3 @@ - import type { Element, ElementContent, Root } from 'hast'; import type { Plugin } from 'unified'; diff --git a/packages/core/src/markdown/rehype-plugins/strip-vscode-spans.ts b/packages/core/src/markdown/rehype-plugins/strip-vscode-spans.ts index e2a96d1a7..3c8d88b7c 100644 --- a/packages/core/src/markdown/rehype-plugins/strip-vscode-spans.ts +++ b/packages/core/src/markdown/rehype-plugins/strip-vscode-spans.ts @@ -1,4 +1,3 @@ - import type { Element, Root } from 'hast'; import type { Plugin } from 'unified'; diff --git a/packages/core/src/markdown/resolve-image-url.ts b/packages/core/src/markdown/resolve-image-url.ts index 0389575ac..cf2c9da8e 100644 --- a/packages/core/src/markdown/resolve-image-url.ts +++ b/packages/core/src/markdown/resolve-image-url.ts @@ -1,4 +1,3 @@ - import { isRelativeUrl } from './safe-url.ts'; function isDevDiagnosticContext(): boolean { diff --git a/packages/core/src/markdown/safe-url.ts b/packages/core/src/markdown/safe-url.ts index 8be632ba4..4d3250d3a 100644 --- a/packages/core/src/markdown/safe-url.ts +++ b/packages/core/src/markdown/safe-url.ts @@ -1,4 +1,3 @@ - export const SAFE_URL_SCHEMES = ['https', 'http', 'mailto', 'tel', 'ftp', 'sms'] as const; const SCHEME_ALT = SAFE_URL_SCHEMES.map((s) => `${s}:`).join('|'); diff --git a/packages/core/src/markdown/serialize-helpers.ts b/packages/core/src/markdown/serialize-helpers.ts index 542204403..46cc6a074 100644 --- a/packages/core/src/markdown/serialize-helpers.ts +++ b/packages/core/src/markdown/serialize-helpers.ts @@ -1,4 +1,3 @@ - import type { Node as PmNode } from '@tiptap/pm/model'; import type { MdxJsxAttribute, MdxJsxExpressionAttribute, MdxJsxFlowElement } from 'mdast-util-mdx'; import type { PropDef, SerializeContext } from '../registry/types.ts'; diff --git a/packages/core/src/markdown/single-dollar-math-promoter.ts b/packages/core/src/markdown/single-dollar-math-promoter.ts index fe73659e6..be97d4d62 100644 --- a/packages/core/src/markdown/single-dollar-math-promoter.ts +++ b/packages/core/src/markdown/single-dollar-math-promoter.ts @@ -1,4 +1,3 @@ - import type { PhrasingContent, Root, Text } from 'mdast'; import type { InlineMath } from 'mdast-util-math'; import { SKIP, visit } from 'unist-util-visit'; diff --git a/packages/core/src/markdown/to-markdown-handlers.ts b/packages/core/src/markdown/to-markdown-handlers.ts index 7bce04302..8f4e9a41c 100644 --- a/packages/core/src/markdown/to-markdown-handlers.ts +++ b/packages/core/src/markdown/to-markdown-handlers.ts @@ -1,4 +1,3 @@ - import type { Nodes, Parents } from 'mdast'; import type { MdxJsxAttribute, MdxJsxExpressionAttribute, MdxJsxFlowElement } from 'mdast-util-mdx'; import type { Handle, Info, State } from 'mdast-util-to-markdown'; diff --git a/packages/core/src/markdown/unknown-mdast-guard.ts b/packages/core/src/markdown/unknown-mdast-guard.ts index c5bfd2386..cd245c38c 100644 --- a/packages/core/src/markdown/unknown-mdast-guard.ts +++ b/packages/core/src/markdown/unknown-mdast-guard.ts @@ -1,4 +1,3 @@ - import type { Root as MdastRoot } from 'mdast'; import type { VFile } from 'vfile'; diff --git a/packages/core/src/markdown/whitespace-char-ref.ts b/packages/core/src/markdown/whitespace-char-ref.ts index ac9f5239b..d578476a7 100644 --- a/packages/core/src/markdown/whitespace-char-ref.ts +++ b/packages/core/src/markdown/whitespace-char-ref.ts @@ -1,4 +1,3 @@ - const INLINE_WHITESPACE_BY_CODE: ReadonlyMap = new Map([ [0x20, ' '], [0x09, '\t'], diff --git a/packages/core/src/markdown/wiki-link-micromark.ts b/packages/core/src/markdown/wiki-link-micromark.ts index afd9936fe..6dad26200 100644 --- a/packages/core/src/markdown/wiki-link-micromark.ts +++ b/packages/core/src/markdown/wiki-link-micromark.ts @@ -17,7 +17,6 @@ declare module 'micromark-util-types' { } } - const CODE_BANG = 33; // ! const CODE_LBRACKET = 91; // [ const CODE_RBRACKET = 93; // ] @@ -261,7 +260,6 @@ export function wikiLinkSyntax(): Extension { }; } - function enterWikiLink(this: CompileContext, token: Token) { this.enter( { @@ -387,7 +385,6 @@ export const wikiLinkToMarkdown: { unsafe: [{ character: '[', inConstruct: ['phrasing'] }], }; - const MICROMARK_EXT = wikiLinkSyntax(); export function remarkWikiLink(this: Processor) {