Compare commits

...
Author SHA1 Message Date
james.maesandCodex GPT-6 baee14f492 docs: prepare user-focused MarkdownViewr product page
Add real reader screenshots, user and developer guides, current contribution/support links, and verified OpenSSF enrollment evidence. Refs #31, #33, #34.

Co-Authored-By: Codex GPT-6 <noreply@openai.com>
Signed-off-by: James Maes <james@kof22.com>
2026-10-12 12:39:59 -05:00
james.maesandCodex b1dc8743b8 feat(release): enable verified Windows signing
Use the published standards 1.1.0 builder with the six-file owned signing catalog. Verified Azure configuration, local final signed distribution and current application acceptance are recorded; production OIDC/signatures remain required before publication.

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <james@kof22.com>
2026-10-12 11:18:59 -05:00
james.maesandCodex 313d25888d docs(release): record verified Azure identity and signing proof
Document the existing signing resource, isolated secretless CI trust and profile-scoped permission, protected release variable readback, and real SHA256/timestamped six-binary verification. Record completed tooltip acceptance while keeping CI and final distribution signing unproved.

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <james@kof22.com>
2026-10-12 09:01:30 -05:00
James MaesandGPT-6 321816c1c9 fix(toolbar): attach disabled-editor tooltip to its native owner
Use the same ToolTip instance in ToolTipService and the hover timer, check the loaded target, and exercise actual hover/text/exit with UI-error assertions. Remove completed timing diagnostics while preserving all prior cases/assertions/deadlines. Refs #32, #28.

Co-Authored-By: GPT-6 <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-12 05:44:02 -05:00
James MaesandGPT-6 de70220b83 test(ui): retain timing evidence for published acceptance
Measure fixture launch, slow lookups, owned closure and Appearance PDF phases in existing hosted artifacts. Preserve every case, assertion and deadline. Refs #21, #22.

Co-Authored-By: GPT-6 <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-12 00:47:15 -05:00
James MaesandCodex d0526ef2f3 test(settings): activate the owned window before native picker actions
Retain original picker assertions and deadlines; include invocation evidence on failure. Hosted causation/acceptance remains required.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-12 00:15:14 -05:00
James MaesandCodex e6fd2818ff test(security): gate seeded parser campaigns and record assurance
Refs #30, #31. Add real parser properties and replay evidence; retain unenrolled badge and signing requirements without bypasses.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 23:31:46 -05:00
James MaesandCodex 82256f362e docs: record application review and remaining release evidence
Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 22:50:30 -05:00
James MaesandCodex 3b01199afc fix(themes): avoid reserved Unicode export filenames
Prefix COM/LPT superscript device names in theme-export suggestions, matching Microsoft's Windows naming rules and existing ASCII/device handling. Six new existing filename cases pass; 668 core tests retain 100% measured line coverage and formatting passes.

Hold the push until the full final PR review is complete. Actual preceding b542 CI is terminal with native/published109 and installed108 per channel, zero skips. No signing/publication or genuine human review claimed.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 22:27:22 -05:00
James MaesandCodex b542dcd1f5 feat(documents): open bounded local links in native readers
Require user-initiated current-namespace Markdown/text links, verify the opened target handle against the source physical folder, and retain that grant for all reloads. Preserve source readers and duplicate focus without arbitrary shell or protocol dispatch.

Validation: 662 core tests and 100% measured coverage, 10 actual document/image filesystem cases, zero-warning Release and formatting. Prepared physical link/duplicate/watcher/junction-reload acceptance; 109 native cases discovered. Prior exact4d full distributed CI is terminal success.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 21:46:44 -05:00
James MaesandCodex 4d346565ea fix(rendering): preserve authored code and format raw HTML text
Reproduce four authored-code delimiter failures, protect actual code/pre regions, and apply enabled formatting only to raw visible text after original source positions are captured. Preserve attributes, comments, entities and the reference metadata/subscript boundary. Keep original MIT attribution and extend real browser acceptance.

Validation: 642 core tests and 100% measured line coverage, zero-warning Release build, full formatting. Previous c3c735e CI is terminal with native/published106 and installed105 per channel, zero skips. New hosted acceptance remains required.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 20:54:15 -05:00
James MaesandCodex a5bccf862a docs(signing): record verified gclo release reference
Verify the existing gclo release checksum and exact executable with Microsoft SignTool, record its full publisher subject, and retain the distinction from unconfigured MarkdownViewr signing. Update actual native placement acceptance.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 20:24:17 -05:00
James MaesandCodex c4047c746a docs(release): reconcile remaining work with verified acceptance
Add a current release checklist backed by actual distributed test cases and fresh placement screenshots, preserve all historical scope, and record the pending shared builder decision.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 20:21:22 -05:00
James MaesandCodex 216830e94f docs(release): record complete distributed acceptance and tenant discovery
Retain actual terminal native, published and stable/dev installed results. Distinguish public directory discovery from still-unverified Azure signing resources; preserve current hosted placement acceptance.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 20:13:42 -05:00
James MaesandCodex c3c735edd6 fix(settings): keep the full window inside its monitor work area
Center the bounded Settings window rather than retaining an offscreen cascading position. Record actual26/26 focused and106/106 native success and preserve current distributed acceptance.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 19:51:04 -05:00
James MaesandCodex 9d5c0d1d51 docs(release): retain terminal palette and repeat-click failures
Record exact1bfab22 focused/native outcomes and actual published timeout evidence before pushing the prepared metadata and native realization corrections.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 19:28:30 -05:00
James MaesandCodex b1f8af75a6 test(themes): realize offscreen choices with native scrolling
Navigate the owned virtualized list before selecting its exact theme automation ID, preserving every value assertion and the original lookup deadline.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 19:23:42 -05:00
James MaesandCodex 49f0556bf9 fix(navigation): retain heading identity on Contents row content
Preserve the trusted heading Tag after adding native depth-marker layout, so repeat ItemClick can navigate an already selected heading. Retain original physical assertions and correct client-versus-screen diagnostic interpretation.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 19:18:04 -05:00
James MaesandCodex 1bfab22835 feat(settings): preview theme palettes and clarify saved defaults
Use accessible native palette/state rows and one default radio choice inside the existing copied draft. Close every owned test window before the unchanged FlaUI exit fallback to avoid intentional sibling-window waits.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 19:12:06 -05:00
James MaesandCodex 24a464eac6 feat(settings): show effective Markdown default-viewer status
Read the Windows selected ProgID with bounded Unicode interop and exact installed ownership, refresh General without changing defaults, and show full-title tooltips only for actual native truncation with physical acceptance prepared.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 18:55:16 -05:00
James MaesandCodex b2ecc8c6ef docs(release): record terminal reader regression evidence
Retain actual published98/100 results and distinguish unreached installed lifecycle checks from the prepared source/Printing correction.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 18:44:06 -05:00
James MaesandCodex 7fa9f7735f feat(settings): share printing defaults with the output dialog
Add native Printing preferences with Apply/Restore and a shared validated options editor. Checked saves refresh all reader baselines, preserve concurrent Settings drafts and update the available named themes.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 18:42:32 -05:00
James MaesandCodex ec78b5020a fix(reader): retain cross-block headings and native click targets
Reproduce multiline inline closing tags in the following HTML block. Preserve complete token spans, place Contents tooltips away from rows and locate the observed exact picker edit through owned enumeration.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 18:40:03 -05:00
James MaesandCodex b5e519eddf docs(release): retain installed registration and coexistence evidence
Record exact hosted53755ab acceptance and distinguish unsigned fixture proof from remaining production signing and Windows chooser checks.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 18:27:45 -05:00
James MaesandCodex aeeb28dd5d feat(settings): add native Contents presentation preferences
Move new-window panel defaults and file-type guidance into General. Add full heading wrapping, optional depth markers and sibling-safe saved presentation, retaining the native depth control and exact heading titles.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 18:26:16 -05:00
James MaesandCodex 5803fc1b5a fix(source): retain complete inline heading syntax when folded
Pair actual inline heading nodes and exercise multiline headers, code/comment lookalikes and implicit closes. Record the Mac settings comparison and exact validation evidence.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 18:18:56 -05:00
James MaesandCodex 53755ab144 test(print): retain published fixture startup failure evidence
Retain owned activity logs, screenshot and accessibility tree when the original print fixtures cannot render their first heading. Add per-case console progress to full published/installed runs. All assertions, cases, startup and process deadlines remain unchanged.

Retained bd3d72a has full native 99/99 and focused 21/21; published log shows two startup failures before the 600-second timeout. Build/format, 17 relevant tooling tests, all configured analyzer rules and shared Markdown lint pass.

Refs #22

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 17:52:13 -05:00
James MaesandCodex 489af757e7 docs(release): record pinned rendering fidelity evidence
Verify immutable upstream template/CSS-help blob hashes and record matching shared screen declarations plus native Contents/source CSS boundaries. Retain full native 99/99 and focused 21/21 on bd3d72a while preserving the live installed run. Shared standards Markdown lint policy passes.

Refs #10

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 17:46:07 -05:00
James MaesandCodex 5de41252f1 test(release): verify simultaneous stable and dev installations
Keep both actual editions installed and open during AppKit updates, verify isolated profile logs and registrations plus unchanged sibling process/bytes/settings, and require stable uninstall to preserve dev. Gate every smoke uninstall on the canonical artifact boundary and actual package manifest.

All 64 tooling tests and every configured analyzer rule on changed scripts pass. Real hosted coexistence execution remains pending; no local desktop input used.

Refs #14

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 17:39:42 -05:00
James MaesandCodex bd3d72abb8 fix(source): retain complete multiline HTML headings when folding
Use parser token spans for raw heading ends, preserving authored multiline attributes, nested title markup and closing syntax. Three newline regressions reproduce the previous header loss; all 589 core tests and required 100% line coverage pass.

Refs #10

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 17:32:31 -05:00
James MaesandCodex 764c7ac7fb feat(release): prepare 1.0 and verify installed file registrations
Refs #14 and #10. Add read-only real install/update/uninstall/default-choice assertions and raw HTML heading/fold/find/reload acceptance. Prior9994eda is terminal green across native, published and both installed channels.

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 17:18:50 -05:00
James MaesandCodex c89ce5cbb7 chore(source): remove temporary native diagnostic probes
Refs #17 and #27. Exact9994eda passes20 focused and98 native cases; keep its live published/installed run intact before pushing this clean candidate.

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 16:43:08 -05:00
James MaesandCodex 9994eda630 fix(source): use content coordinates for offscreen scroll anchors
Refs #17. Retain bounded geometry probes until unchanged native and published acceptance verifies the correction.

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 16:31:16 -05:00
James MaesandGPT-6 6374611537 fix(source): repaint on explicit shell theme mode changes
A System-to-Light mode change can retain ActualTheme, so repaint the source from the actual shell command as well as resolved-theme events. Extend real native palette coverage through System-to-Light on already highlighted text, and require three consecutive attribute observations to catch the proven correct-then-plain color reset. Keep original colors, selection and read-only assertions.

Co-Authored-By: GPT-6 <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 16:00:47 -05:00
James MaesandGPT-6 b15fcbce5f fix(source): include scroll offsets in native caret hit testing
The actual focused regression returns character2 at verticaloffset5297.759. WinUI exposes the full text extent as RichEdit client space, so add current horizontal and vertical offsets to the client inset before native hit-testing. Keep the original physical source alignment assertions and bounds.

Co-Authored-By: GPT-6 <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 15:56:55 -05:00
James MaesandGPT-6 5fa6754f0a fix(source): paint tokens after native foreground propagation
The published trace shows the native heading changing from #75628B to #1F1F1F after painting with the same snapshot, generation and six tokens. Await the owned low-priority dispatcher boundary after setting control brushes, then apply default and semantic native ranges. Recheck stale/disposal guards; preserve actual color, pointer, selection and read-only assertions.

Co-Authored-By: GPT-6 <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 15:46:04 -05:00
James MaesandGPT-6 91877f7688 fix(source): select the visible native caret before focus
The hosted probe shows that offscreen range rectangles are zero before adding the scroll offset, so binary searching them selects character zero and native focus reveals the old caret. Hit-test the actual visible native text instead. Outline DOM probes prove nested hidden state survives parent expansion; await the corresponding real UIA peer transition with the existing bound and identical absence assertions.

Co-Authored-By: GPT-6 <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 15:37:34 -05:00
James MaesandGPT-6 04c1da8289 test(reader): retain native failure evidence before geometry queries
Preserve source alignment failures when UIA glyph queries throw E_UNEXPECTED. Capture opt-in native color propagation and generated collapsed-section indices to distinguish the remaining source and outline defects; keep all acceptance assertions intact.

Co-Authored-By: GPT-6 <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 15:29:10 -05:00
James MaesandCodex dbda6d48da test(installation): require channel-owned file registrations
Mark native tests as published or installed from the actual smoke harness's
verified manifest/channel/update boundary, saving and restoring the marker.
Installed Settings must find this exact executable's expected registration
and enable Default Apps; a missing registration plus disabled button cannot
silently pass. Published fixtures must remain unregistered.

The marker changes no application behavior and creates no registry state.
Windows user selection, Explorer and lifecycle acceptance remain required.

Validation: zero-warning Release, format, 55 script boundary cases and both
script analyzer partitions; actual installed execution pending.
Refs #14

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 15:15:12 -05:00
James MaesandCodex efe14af2fa fix(source): repaint after native theme propagation
Initial native source foreground is intermittently reset to#1F1F1F following
ThemeLight even when its real UIA Toggle action executes. Queue semantic paint
after native theme propagation instead of painting inside ActualThemeChanged.
Preserve parser/range generation guards, native selection and read-only state.

Keep exact native foreground and physical hover regressions; focused14/15 does
not override current full93/97 proof. Include the newly observed nested outline
variant in focused CI, retaining all existing cases and release gates.

Validation: zero-warning Release and full format; exact hosted proof pending.
Refs #16, #27

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 15:12:02 -05:00
James MaesandCodex 2fe5853720 fix(activation): release accepted broken pipe connections
A synchronous broken read clears IsConnected before cleanup; skipping Disconnect
leaves the native instance busy and prevents later file activations. Track each
accepted connection and release it even after the managed Broken transition.

Add a deterministic real current-user pipe regression: defer the actual server
continuation until its peer closes, then assert the next real acknowledgement.
The unfixed next connection times out10seconds; all4 cases and25 stress runs /
100tests pass after the fix. No retries, deadlines or assertions were relaxed.

Include pipe cases in the required focused job. Retain numeric source focus
coordinates behind the existing test-only trace opt-in for the sole remaining
focused source alignment failure; remove that tagged probe before release.

Validation: zero-warning Release build, full format verification and actual
100-case pipe stress. Prior9fdfc2d focused14/15; distributed proof pending.

Refs #1, #10, #14, #16, #17

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 15:08:15 -05:00
James MaesandCodex 9fdfc2d981 fix(reader): respect heading insets and native menu actions
Compare heading navigation against its actual CSS scroll target so repeated
commands advance while preserving Darin's original scroll inset. Assert the
exact native 16px landing position.

Use WinUI's supported Toggle action instead of a focus-sensitive mouse
fallback; physical source hover assertions remain. Navigate the native picker
before entering all four quoted filenames to avoid its observed input limit.
Include every remaining native failure in the focused required CI check.

Validation: zero-warning Release build, full format verification, 55 Pester
boundary tests and both analyzer partitions pass. Prior 8abbd4d native run is
93/97; this correction awaits exact-head native/published/installed proof.

Refs #1, #10, #16, #17, #21, #27

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 14:56:57 -05:00
James MaesandGPT-6 8abbd4d43c fix(reader): expose the reading scroller and preserve native focus
Name and focus the original document scroll region for real browser UIA
scrolling. Defer duplicate-reader activation until menus finish closing.
Use visible menu peers and preserve Settings focus during UIA invocation.

Retain exact font-growth, PDF width/edge, source-color and palette assertions.
Give the font fixture a clear rounding margin and center its narrow print
body; sample the real empty reading gutter. Record actual failure values.

Validation: zero-warning Release build;586 core tests and100% measured line
coverage; native84/97 on the prior head;75 real pipe stress tests pass.
Current hosted/published/installed and production signatures remain pending.

Co-Authored-By: GPT-6 <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 14:39:15 -05:00
James MaesandGPT-6 05bf612779 fix(reader): correct print width and native acceptance boundaries
Reset the outer custom body width for full-page printing and align fixed
Contents/Print controls with the standard toolbar hit targets. Use the
observed native Open picker filename control and visible outline-depth peer.

Keep the exact existing pixel, width, scroll and font-growth assertions;
retain native failure geometry and run a focused hosted regression group.
Record current74/97 native acceptance and the passing source-hover proof.

Validation: zero-warning Release build;586 core tests;100% measured line
coverage; full formatting verification. Current distributed acceptance and
production signing remain pending.

Co-Authored-By: GPT-6 <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 14:19:38 -05:00
James MaesandCodex 0ed608e09c feat(reader): isolate editions and refine native review behavior
Use separate stable/dev package IDs and compose native services from the actual
installed channel after Velopack startup. Keep titles, settings, logs and update
identity separate; installed fixtures now pack distinct IDs and assert edition
titles without touching production installs. Add a reproducible gclo-family
Markdown icon and WinUI open-source credit with complete vendor notices retained.

Hosted0eb3a0e reproduced the source-hover bug: heading color changes from
#75628B to #000000 at the first pointer assertion. Test the native visual-state
foreground hypothesis by removing only ContentElement.Foreground animations;
keep native chrome and actual character-color assertions. Add a focused hosted
regression with retained TRX alongside the unchanged full acceptance scope.

Record exact current failures and immutable GitHub OIDC claims. Prepare deployed
Azure account/profile/full-subject inspection and profile-scoped signer actions;
no Azure mutation or actual production signature is claimed.

Validation: zero-warning Release, 586 core tests/100% measured lines, formatting,
55 script tests and both changed-script analyzer partitions pass locally. Native
candidate/edition acceptance and all 25 current full-suite failures remain open.

Refs #1, #14, #16, #24, #27, #29

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 14:00:47 -05:00
James MaesandCodex 0eb3a0efbd feat(reader): complete configuration and Windows integration
Add cancel-safe native Settings pages, configurable typography/CSS/themes,
file registration and activation, theme-aware source and proportional layout,
toolbar/footer/editor guidance and open-source credits. Preserve attributed
Darin Kelkhoff document styles and literal passive rendering. Retain verbatim
vendor notices with scoped newline/whitespace preservation; secret, security
and other hooks remain enabled.

Validation: zero-warning Release build, 582 core tests with 100% measured line
coverage, 25 Windows adapter tests, formatting and self-contained publish pass.
Current native/distributed acceptance and source-hover diagnosis remain pending;
no production release-readiness claim.

Refs #1, #6, #11, #14, #16, #17, #18, #19, #20, #21, #23, #24, #25, #26, #27, #28

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 13:40:39 -05:00
James MaesandCodex 841798c750 test(documents): drive owned native controls after navigation
Reacquire browser glyphs, click unsupported menu providers physically and retain exact picker diagnostics. Keep individual waits unchanged; allow the measured expanded full suite to emit its TRX within a bounded 600-second process budget.

Refs #1, #14

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 13:20:03 -05:00
James MaesandCodex 1cfc07f900 docs: record full print evidence and window milestone
Refs #1, #12, #14

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 04:46:02 -05:00
James MaesandCodex e48a08cd56 feat(documents): add native windows and private recent files
Refs #1, #14

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 04:36:28 -05:00
James MaesandCodex bf4fcdc26a test(print): measure precise font scale in actual PDFs
Refs #12, #13

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 04:25:11 -05:00
James MaesandCodex 6e17674494 test(reader): retain actual zoom failure geometry
Refs #12, #13

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 04:15:28 -05:00
James MaesandCodex d0a225d420 test(print): verify actual font and layout options
Compare real PDF glyph scaling and padding, user-theme content width, optional border rows and full-page output through native controls. Verify actual paired find/source selection and screen paint after successful exports. Preserve original print cases and retain generated PDF/PNG evidence.

Refs #12

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 04:01:02 -05:00
James MaesandCodex 300e14526c chore(print): retain verified acceptance and remove probes
Original hosted print cases pass: both cancels, actual default/themed PDF pixels and unchanged rendered/source/collapse state. Remove the temporary workflow and numeric probes; retain original regressions and require full clean native/published/stable/dev gates. Record measured standards readiness and remaining inventory/review/signing work.

Refs #12, #1

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 03:47:33 -05:00
James MaesandCodex ec5f98fbf4 test(print): await the complete native PDF filename
The hosted native edit reports a typed prefix while queued input is still processing. Wait for exact full readback before Save rather than asserting an incomplete snapshot; keep PDF output and reading-state assertions unchanged.

Refs #12

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 03:42:17 -05:00
James MaesandCodex 6bfcf0c52e fix(print): restore reading position after native PDF output
Hosted evidence confirms PDF generation moves the same screen glyph by 41 pixels. Restore validated vertical geometry after the awaited PDF operation only on the same ready navigation; assert actual glyph/view rectangles instead of a stale 200-percent UIA range.

Refs #12

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 03:38:04 -05:00
James MaesandCodex 39f4ea3983 test(print): compare actual glyphs with scroll-provider state
The first viewport trace reports an impossible 200 percent before printing. Pair each print stage with actual heading and browser rectangles before choosing a restoration change.

Refs #12

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 03:29:43 -05:00
James MaesandCodex eba7a752fd test(print): measure settled viewport around PDF output
Record fixture-only viewport stages and await stable native layout before taking the unchanged-state baseline. Preserve the original PDF pixel and after-print state assertions.

Refs #12

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 03:25:15 -05:00
James MaesandCodex 41a687d17c test(print): drive committed native PDF destinations
Use the established find control identities. Exercise real filename typing with exact readback to diagnose a saved PDF missing from its expected fixture path.

Refs #12

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 03:19:42 -05:00
James MaesandCodex 28c295f3fe feat(print): add native printing and independent print styles
Keep reading and print styles in separate media sheets; preserve viewport, source, collapse, find and window font zoom. Add saved reference print choices, Windows print UI and explicit PDF save through the installed App SDK picker. Core363/100% lines, build/format/docs and55 script tests pass. Three hosted native/output cases are pending; they cancel physical print UI and rasterize owned PDFs using Windows, without a new renderer dependency.

Refs #12, #1. ADR-0003 proposed for maintainer review.

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 03:12:17 -05:00
James MaesandCodex 2639e2d8d2 test(source): retire diagnostics after the original regression passes
Original source viewport test passed on 690be07 in run 38121389088. Remove temporary workflow, clicked-content/focus probes and geometry traces; keep the actual browser/source/physical-wheel regression and full required suites. Preserve historical session notes and keep startup state concise.

Refs #10, #11, #13.

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 02:32:08 -05:00
James MaesandCodex 690be072d9 fix(navigation): resolve the heading from clicked native content
WinUI ItemClick supplies the TextBlock content rather than the ListViewItem. Attach the numeric heading identity to both native elements and accept the tagged clicked content. The original physical repeat-navigation regression reproduces the mismatch; no source scroll mapping or assertion changes.

Refs #10, #11.

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 02:26:35 -05:00
James MaesandCodex dcddefb9c7 test(navigation): trace the native repeated activation boundary
Refs #10, #11. Physical repetition also fails; trace the opted-in numeric ItemClick boundary and assert actual browser alignment before source alignment.

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 02:18:33 -05:00
James MaesandCodex 016c7453a4 test(navigation): activate the selected heading with physical input
Refs #10, #11. Preserve the original glyph and wheel assertions; distinguish native ItemClick from the UIA selection path.

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 02:13:28 -05:00
James MaesandCodex 2bf1d14570 fix(navigation): reactivate an already selected outline heading
The original source regression now passes actual native and browser middle-heading alignment after bounded physical wheel input. It then reproduces a same-heading click that never jumps back because selection is unchanged. Handle native ItemClick as well as SelectionChanged; preserve keyboard selection jumps and bounded numeric renderer commands.

Zero-warning build, format, docs and normal hooks pass. The existing regression is retained unchanged; hosted full/native/installed proof remains required.

Refs #9, #10, #13

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 02:04:26 -05:00
James MaesandCodex 22a33937b6 test(source): bound physical wheel input per native event
Measured input moves source and reader correctly, but a 31.8-detent event moves only one 580px viewport. Send at most four detents per event, read settled native movement after each, and retain exact glyph adjustment and all original bidirectional/wheel assertions. No application change.

Zero-warning build, format, docs and normal hooks pass. Existing 330 core tests/100% measured coverage and 55 script tests remain; hosted proof pending.

Refs #10, #13

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 01:54:55 -05:00
James MaesandCodex 05c4bc91b9 test(source): drive measured physical wheel scrolling
The UIA range accepted min0/max7370 and value2455.883 but did not reveal the middle glyph or produce a new source geometry frame. Exercise the user gesture instead: measure one settled physical wheel detent, approach the text region, then align from actual native glyphs. Retain all source/reader alignment and wheel assertions; no application change.

330 core tests/100% measured lines retained; zero-warning build, format, documentation and hooks pass. Hosted proof remains pending.

Refs #10, #13

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 01:44:12 -05:00
James MaesandCodex 06a1213eff test(source): retain native scrollbar range diagnostics
The expanded native suite passes 48/49 including actual zoom glyph/key/toolbar/reload acceptance. Preserve the original source failure and record native scrollbar min/max/read-only/value plus owned geometry before another driver change. No app change or relaxed assertions.

Refs #10, #13

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 01:35:08 -05:00
James MaesandCodex 0f13849017 feat(reader): add window-local font zoom and native controls
Adapt pinned reference font-size scaling, 1.1 steps and 30–500% bounds without changing line height or persisted theme JSON. Ctrl+plus/equal/minus/0 retain owned reading focus; native menu and accessible percentage toolbar share the command. Navigation retains viewport/collapse and existing find replay.

Source focus alignment passes on 1c4d1ad. Drive the actual native scrollbar after the observed no-op text-range request, retaining exact source/reader glyph and physical-wheel assertions. Separate optional diagnostics from required CI.

330 core tests/100% measured lines, zero-warning build, format, docs, all 55 script tests and normal hooks pass. Hosted 49 native/published and 48 installed tests remain pending; no local GUI input.

Refs #10, #11, #13

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 01:28:00 -05:00
James MaesandCodex 1c4d1adaca fix(source): align the visible caret before native focus
Hosted diagnostics show GettingFocus captures the correct 5311 offset but later pointer/caret processing resets it to zero. Position only an unselected read-only caret in the visible text before focus; retain active find and viewport guards. Exact full UI acceptance remains required.

Allow the enlarged full published/installed suites a bounded five minutes and retain process output before reporting timeout failure. An owned headless timeout probe and both script-analyzer passes passed.

Refs #10, #11

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 01:12:44 -05:00
James MaesandCodex 82898bd278 test(source): isolate viewport diagnostics without masking timeouts
Keep full required UI suites intact. Run the exact failing source case on a read-only hosted Windows job and print owned focus logs before diagnostic range reads.

Refs #10, #11

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 01:06:42 -05:00
James MaesandCodex f650e36642 feat(reader): manage editor lists and preserve source focus
Add stable named editor choices, bounded legacy migration, copied add/remove/reorder drafts and native file/folder launch menus. Preserve one literal process argument and add actual hosted selection/persistence/probe tests. Restore the synchronized source viewport after native focus/caret revelation, with actual glyph assertions retained.

Refs #10, #11

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 00:53:21 -05:00
James MaesandCodex 2dc509dcfa test(source): verify native movement before reader alignment
Physically focus the owned native source before its text-range scroll request and require actual source glyph alignment before reverse synchronization. Retain original heading bounds and physical-wheel assertions; trace actual source offset changes numerically.

Refs #10

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 00:36:26 -05:00
James MaesandCodex 90b5406fe4 fix(source): map original offsets to native CR text
Hosted source selections exposed a two-character drift after two newlines. Preserve canonical offsets and normalize only the native projection to one CR per break; retain exact viewport, fold and find assertions.

Refs #10

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 00:28:30 -05:00
James MaesandCodex ddb98ac286 feat(source): mirror folds and synchronize reading positions
Preserve canonical source/find snapshots while projecting literal nested folds and pairing native/browser heading geometry. Add bounded numeric frame validation and actual hosted viewport/fold/find/reload acceptance.

Refs #10

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-11 00:20:40 -05:00
James MaesandCodex b0f3b8638f perf(source): index original line boundaries once
Avoid scanning the remaining original text for every heading on a long raw HTML line; verify 300 distinct same-line anchors. Record full native/published/installed vim acceptance while retaining full source synchronization scope.

Refs #10

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 23:53:48 -05:00
James MaesandCodex a0d8fe73aa feat(source): map sanitized headings to original syntax
Carry parser-derived UTF-16 provenance through sanitation using fresh private markers that are stripped before presentation. Cover Setext, fences, raw HTML, nesting, duplicates, Unicode, frontmatter and forged attributes. Source scroll and collapse UI remains pending.

Refs #10

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 23:49:19 -05:00
James MaesandCodex 6bf125d7fd test(navigation): focus the reader with physical input
The first vim producer retained all 41 existing successes but UIA focus left native logical focus empty or on the outline button. Use the proven real MOVE/Down/Up path inside exact owned browser bounds, keeping real viewport, heading and query-typing assertions unchanged.

Refs #10

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 23:39:46 -05:00
James MaesandCodex dfc41a78bd feat(navigation): add native vim reading commands
Route bounded reading commands through the owned UI thread with text-focus, modifier, layout and timed-prefix guards. Add physical viewport, heading and query-typing acceptance and preserve reference attribution.

Refs #10

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 23:31:02 -05:00
James MaesandCodex 5a9af6328f fix(navigation): reveal same-document fragment targets
Route same-document SourceChanged events through an exact-document and bounded-heading policy. Rearm repeated links after recollapse; retain physical click and actual reveal outcomes. Core 206 tests/100% lines and build/format/docs pass.

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 23:07:20 -05:00
James MaesandCodex 9edd982e41 fix(tests): account for the composed browser hit provider
Require the physical pointer inside the exact hyperlink bounds and its owned browser provider. Retain the real click and collapsed heading/body reveal outcomes; record the proven native Ctrl+F acceptance.

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 22:57:21 -05:00
James MaesandCodex 574b57432a feat(editor): configure literal external document launch
Persist a validated editor executable, expose native configuration and launch commands, and preserve the viewer on missing files or launch errors. Add bounded argument tests and hosted actual-process fixtures; editor-list and folder parity remain tracked in issue #11.

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 22:55:12 -05:00
James MaesandCodex 2292603569 fix(input): route owned browser shortcut messages
Limit the Ctrl+F adapter to this foreground window and UI thread. Keep physical keyboard/link assertions and retain exact focus, pointer and fixture screenshot evidence.

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 22:51:10 -05:00
James MaesandCodex 7d7c712c63 fix(find): register the window shortcut before menus open
Keep the physical browser-focus shortcut test and identify collapsed browser headings separately from outline labels. Correct ADR spacing and record the terminal acceptance evidence.

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 22:41:19 -05:00
James MaesandCodex 4f4280921c feat(find): pair native document and source search
Add one Ctrl+F toolbar with literal source positions, native browser highlights, shared counts and wrapping navigation. Serialize query/reload updates, restore section choices and keep scripts/messages/host objects disabled. Add real keyboard, selection, viewport, hostile-query/reload and fragment-link acceptance.

Refs: #1, #9, #10

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 22:30:13 -05:00
James MaesandCodex 4bf0b81b85 fix(ui): expose source and inject native pointer movement
Declare the native source surface before the embedded browser without changing its visual column. Replace cursor-only warping with checked SendInput movement after hosted traces showed drag start/completion without deltas; retain physical mode and resize assertions and add source-divider acceptance.

Refs: #8, #9, #10

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 22:00:22 -05:00
James MaesandCodex 9ff820f477 feat(source): show literal snapshots beside the reader
Persist source visibility and width, refresh the committed snapshot on reload, and keep the reader usable with both panes open. Add hosted literal/read-only/reload acceptance and opt-in native event traces for the two outstanding pointer failures.

Refs: #8, #9, #10

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 21:50:34 -05:00
James MaesandCodex d59dba2c96 fix(tests): process pointer states before releasing native input
Refs #8, #9. Keep mode/table/resize assertions and timeouts; settle actual mouse down/move/up and retain resize diagnostics.

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 21:31:19 -05:00
James MaesandCodex 443b4928ee fix(ui): target submenu input and expose the resize surface
Refs #8, #9. Keep settings/table/physical-resize assertions; verify pointer identity and dismiss the depth popup before dragging.

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 21:23:09 -05:00
James MaesandCodex ce9afc6237 fix(ci): isolate casing analysis from competing rule lookups
Refs #5. Preserve every configured rule and fail closed; verify both partitions with negative fixtures. All 13 files and a bounded 20-case race probe pass locally.

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 21:18:55 -05:00
James MaesandCodex 5c3988e012 feat(navigation): add native outline and section controls
Refs #9, #8. Preserve exact frontmatter assertions with fixture diagnostics; 161 core tests and 100% line coverage pass.

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 21:15:47 -05:00
James MaesandCodex a141929820 fix(frontmatter): expose metadata table semantics
Refs #8. Add a caption and row headers; keep the actual accessible-table assertion unchanged.

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 21:03:18 -05:00
James MaesandCodex 3b6ebf83d0 feat(frontmatter): add persisted metadata display modes
Refs #8, #7. Also verify the native code-font size control.

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 20:58:14 -05:00
James MaesandCodex d3530d106f chore(ci): report analyzer files and engine exceptions
Refs #5. Keep every configured rule and propagate analyzer failures.

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 20:58:10 -05:00
James MaesandCodex 30ca0943c0 feat(themes): edit and restore persistent document themes
Refs #7

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 20:44:51 -05:00
James MaesandCodex 77dd04da96 fix(accessibility): declare native status before the browser peer
Refs #6, #7

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 20:33:36 -05:00
James MaesandCodex fa0df784b3 fix(window): keep document status inside the working display
Refs #6

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 20:27:28 -05:00
James MaesandCodex 0507ca5ac4 feat(themes): port the original document palettes
Refs #7

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 20:22:31 -05:00
James MaesandCodex e7b6985a34 test(accessibility): retain native status layout evidence
Refs #6

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 20:20:23 -05:00
James MaesandCodex 23928bc614 fix(accessibility): expose document status to automation
Refs #6

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 20:15:01 -05:00
James MaesandCodex 665ece7fa4 test(documents): diagnose deletion recovery status
Refs #6

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 20:11:02 -05:00
James MaesandCodex 09d14e6b78 feat(documents): reload saves and retain the reading position
Refs #6

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 19:59:49 -05:00
James MaesandCodex f1756d21ed feat(reader): open and render isolated Markdown documents
Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 19:35:37 -05:00
James MaesandCodex 85004f1d1f docs: record Windows viewer bootstrap and repository identity
Preserve the generated repository name in setup examples and record actual foundation validation, hosted run IDs and remaining viewer/release requirements.

Refs: #1

Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
2026-10-10 19:06:41 -05:00
253 changed files with 40904 additions and 860 deletions

No files matched your search

+3
View File
@@ -4,3 +4,6 @@
*.ico binary
*.cmd text eol=crlf
*.bat text eol=crlf
# Retain verbatim vendor license/notice bytes, including original whitespace.
THIRD-PARTY-LICENSES/** -text whitespace=-trailing-space,-blank-at-eof
+1 -1
View File
@@ -11,7 +11,7 @@ body:
id: version
attributes:
label: Version
description: The exact version (Help, About, or `--version`).
description: The exact version shown in Help → About, including stable or dev edition.
validations:
required: true
- type: textarea
+6
View File
@@ -1,5 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: User guide
url: https://github.com/KofTwentyTwo/MarkdownViewr.Windows/blob/main/docs/user-guide.md
about: Opening files, themes, printing, default apps, and troubleshooting.
- name: Community support
url: https://github.com/KofTwentyTwo/MarkdownViewr.Windows/discussions
about: Ask usage questions or discuss ideas with the community.
- name: Report a security vulnerability
url: https://github.com/KofTwentyTwo/MarkdownViewr.Windows/security/advisories/new
about: Please report security issues privately, never as a public issue.
+44
View File
@@ -18,6 +18,48 @@ concurrency:
jobs:
reader-regressions:
name: reader-regressions
runs-on: windows-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up .NET
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
- name: Build locked dependencies
shell: pwsh
run: |
dotnet restore MarkdownViewr.Windows.slnx -p:Platform=x64 --locked-mode
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
dotnet build MarkdownViewr.Windows.slnx -p:Platform=x64 -warnaserror --no-restore
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Verify native source, layout, pickers and print regressions
shell: pwsh
run: >-
dotnet test tests/MarkdownViewr.Windows.UiTests --no-build
--filter 'FullyQualifiedName~SourceHighlighting|FullyQualifiedName~Settings_NewWindowPanelDefaults|FullyQualifiedName~Settings_ContentsPresentation|FullyQualifiedName~Settings_ContentsTooltip|FullyQualifiedName~Themes_PalettePreviewAndDefaultChoice|FullyQualifiedName~Settings_PrintingDefaults|FullyQualifiedName~Settings_PrintingDraft|FullyQualifiedName~ThemeTransfer_NativePickerCancels|FullyQualifiedName~Print_UserWidthAndBorders|FullyQualifiedName~ReadingKeys_ScrollCommands|FullyQualifiedName~Source_ShowReloadAndHide|FullyQualifiedName~ThemeEditor_AllTypography|FullyQualifiedName~ThemeEditor_NewCustomTheme|FullyQualifiedName~Appearance_WidthCssAndPrint|FullyQualifiedName~Toolbar_ReadingCommandsAndMenus|FullyQualifiedName~Toolbar_EditorAndPrint|FullyQualifiedName~ThemeTransfer_BatchFutureInvalidAndDisabledExport|FullyQualifiedName~ReadingKeys_HeadingSequences|FullyQualifiedName~Source_BidirectionalScroll|FullyQualifiedName~Source_RawHtmlHeadingProvenance|FullyQualifiedName~Extensions_NativePreferences|FullyQualifiedName~Windows_LocalDocumentLinks|FullyQualifiedName~ActivationInboxTests|FullyQualifiedName~Outline_NestedCollapse'
--logger 'trx;LogFileName=reader-regressions.trx'
--results-directory artifacts/reader-regressions
- name: Retain native regression evidence
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: reader-regression-results
path: |
artifacts/reader-regressions/*.trx
artifacts/installed-smoke-ui/*.png
artifacts/installed-smoke-ui/*.log
artifacts/installed-smoke-print/*.pdf
artifacts/installed-smoke-print/*.png
retention-days: 7
installed-updates:
name: installed-updates
runs-on: windows-latest
@@ -50,6 +92,8 @@ jobs:
artifacts/installed-smoke-*/*.json
artifacts/installed-smoke-*/*.log
artifacts/installed-smoke-*/*.trx
artifacts/installed-smoke-*/*.png
artifacts/installed-smoke-*/*.pdf
retention-days: 7
ci:
uses: KofTwentyTwo/standards/.github/workflows/dotnet.yml@fdf5ed029603c938615a32b1beca3628b8787945 # v0.1.2
+66
View File
@@ -0,0 +1,66 @@
# K22-TEST-25: reproducible security-property campaigns, without browser execution or profile writes.
name: fuzz
on:
pull_request:
push:
branches: [main]
schedule:
- cron: "34 6 * * 1"
workflow_dispatch:
workflow_call:
permissions: {}
concurrency:
group: fuzz-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
fuzzing:
name: fuzzing
runs-on: windows-latest
timeout-minutes: 15
permissions:
contents: read
env:
K22_FUZZ_ITERATIONS: ${{ (github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/')) && '10000' || '500' }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up .NET
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
- name: Restore and build locked core targets
shell: pwsh
run: |
dotnet restore tests/MarkdownViewr.Windows.Core.Tests -p:Platform=x64 --locked-mode
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
dotnet build tests/MarkdownViewr.Windows.Core.Tests -c Release -p:Platform=x64 -warnaserror --no-restore
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Restore and build activation adapter targets
shell: pwsh
run: |
dotnet restore tests/MarkdownViewr.Windows.UiTests -p:Platform=x64 --locked-mode
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
dotnet build tests/MarkdownViewr.Windows.UiTests -c Release -p:Platform=x64 -warnaserror --no-restore
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Verify seeded parser security properties
shell: pwsh
run: |
Write-Output "Fuzz seeds22023/99173, five targets, $env:K22_FUZZ_ITERATIONS iterations per seed/target."
dotnet test tests/MarkdownViewr.Windows.Core.Tests -c Release -p:Platform=x64 --no-build --no-restore --filter Category=Fuzz --logger 'trx;LogFileName=fuzz.trx' --results-directory artifacts/fuzz
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
dotnet test tests/MarkdownViewr.Windows.UiTests -c Release -p:Platform=x64 --no-build --no-restore --filter Category=Fuzz --logger 'trx;LogFileName=activation-fuzz.trx' --results-directory artifacts/fuzz
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Retain synthetic campaign failures and results
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: fuzz-results
path: artifacts/fuzz/*.trx
if-no-files-found: error
retention-days: 30
+19 -5
View File
@@ -9,7 +9,7 @@
# - Environment `release` (Settings -> Environments) with a deployment rule that allows
# only tags matching v*.
# - Configure verified Azure signing variables and a repository/environment-bound
# OIDC identity; pin the published reviewed builder, enable sign and declare
# OIDC identity; pin the reviewed builder, enable sign and declare
# the owned-file catalog. See docs/release-signing.md. Unsigned publication is blocked.
# - winget (optional, stable releases only): the package must already exist in
# microsoft/winget-pkgs. Submit it once by hand with
@@ -87,9 +87,15 @@ jobs:
permissions:
contents: read
fuzzing:
needs: verify
uses: ./.github/workflows/fuzz.yml
permissions:
contents: read
release:
needs: [verify, gates, security, codeql, scripts]
uses: KofTwentyTwo/standards/.github/workflows/release-velopack.yml@fdf5ed029603c938615a32b1beca3628b8787945 # v0.1.2
needs: [verify, gates, security, codeql, scripts, fuzzing]
uses: KofTwentyTwo/standards/.github/workflows/release-velopack.yml@93b1d6163a4c466ae443e98b62786fc4d80d33bd # v1.1.0
permissions:
contents: write # create and publish the GitHub release
id-token: write # sign attestations; Azure OIDC when signing
@@ -97,7 +103,8 @@ jobs:
with:
solution: MarkdownViewr.Windows.slnx
app-project: src/MarkdownViewr.Windows/MarkdownViewr.Windows.csproj
app-id: markdownviewr-windows
# Separate package IDs install side by side; the installed channel selects its title/data root.
app-id: ${{ contains(github.ref_name, '-') && 'markdownviewr-windows-dev' || 'markdownviewr-windows' }}
main-exe: MarkdownViewr.Windows.exe
icon: src/MarkdownViewr.Windows/Assets/Brand/app.ico
platform: x64
@@ -105,7 +112,14 @@ jobs:
coverage-packages: MarkdownViewr.Windows.Core
test-projects: |
tests/MarkdownViewr.Windows.Core.Tests
sign: false # Preflight blocks publication until the reviewed signing builder is published and configured.
sign: true
signing-files: |
app/MarkdownViewr.Windows.exe
app/MarkdownViewr.Windows.dll
app/MarkdownViewr.Windows.Core.dll
app/KofTwentyTwo.AppKit.dll
app/KofTwentyTwo.AppKit.Updates.dll
app/KofTwentyTwo.AppKit.WinUI.dll
# winget lives here, not in the shared workflow, because its token belongs to this
# repository's `release` environment. It runs only for stable releases, and only when
+20 -2
View File
@@ -51,10 +51,28 @@ jobs:
$ErrorActionPreference = 'Stop'
Install-Module PSScriptAnalyzer -RequiredVersion 1.25.0 -Scope CurrentUser -Force
Import-Module PSScriptAnalyzer -RequiredVersion 1.25.0
# Analyze each file separately: recursive analysis can crash inside 1.25.0.
# Negative fixtures prove neither partition can silently lose its rules.
$casingFixture = @(Invoke-ScriptAnalyzer -ScriptDefinition "get-content -literalpath 'fixture.txt'" -Settings ./PSScriptAnalyzerSettings.psd1 -IncludeRule PSUseCorrectCasing)
if('PSUseCorrectCasing' -notin $casingFixture.RuleName) { throw 'The casing pass did not detect incorrect casing.' }
$remainingFixture = @(Invoke-ScriptAnalyzer -ScriptDefinition "Write-Host 'fixture'" -Settings ./PSScriptAnalyzerSettings.psd1 -ExcludeRule PSUseCorrectCasing)
if('PSAvoidUsingWriteHost' -notin $remainingFixture.RuleName) { throw 'The remaining-rule pass did not detect Write-Host.' }
# Keep casing in its own pass: command metadata races other rules in 1.25.0.
# https://github.com/PowerShell/PSScriptAnalyzer/issues/1708
foreach($script in Get-ChildItem -LiteralPath build -Recurse -File | Where-Object Extension -In '.ps1', '.psm1', '.psd1')
{
$findings = @(Invoke-ScriptAnalyzer -Path $script.FullName -Settings ./PSScriptAnalyzerSettings.psd1)
Write-Output "Analyzing $($script.Name)"
try
{
$findings = @(
Invoke-ScriptAnalyzer -Path $script.FullName -Settings ./PSScriptAnalyzerSettings.psd1 -IncludeRule PSUseCorrectCasing
Invoke-ScriptAnalyzer -Path $script.FullName -Settings ./PSScriptAnalyzerSettings.psd1 -ExcludeRule PSUseCorrectCasing
)
}
catch
{
Write-Output $_.Exception.ToString()
throw
}
if($findings.Count -gt 0) { $findings | Format-Table; throw "Script analysis failed: $($script.Name)" }
}
- name: Verify script boundary tests
+3
View File
@@ -24,9 +24,12 @@ repos:
- id: check-toml
- id: detect-private-key
- id: end-of-file-fixer
exclude: ^THIRD-PARTY-LICENSES/
- id: mixed-line-ending
exclude: ^THIRD-PARTY-LICENSES/
args: [--fix=lf]
- id: trailing-whitespace
exclude: ^THIRD-PARTY-LICENSES/
args: [--markdown-linebreak-ext=md]
- id: no-commit-to-branch
args: [--branch, main]
+1 -1
View File
@@ -2,7 +2,7 @@
## Purpose and Structure
MarkdownViewr.Windows is a Windows Markdown viewer built with AppKit and WinUI 3. Read `docs/PRODUCT.md`, `docs/TODO.md` and `docs/SESSION-STATE.md` before continuing. The initial greeting is scaffold content; do not claim the viewer is implemented until its requirements pass.
MarkdownViewr.Windows is a Windows Markdown viewer built with AppKit and WinUI 3. Read `docs/PRODUCT.md`, `docs/TODO.md` and `docs/SESSION-STATE.md` before continuing. The first reader is implemented; full parity and desktop acceptance remain pending. Do not claim requirements passed without evidence.
`src/MarkdownViewr.Windows.Core/` contains testable logic and view models. `src/MarkdownViewr.Windows/` contains WinUI views and Windows adapters; brand assets are in `Assets/Brand/`. Unit, desktop UI and installed-update probes live under `tests/`. `build/` contains coverage, packaging, setup and release validation tools.
+25 -107
View File
@@ -1,127 +1,45 @@
# Contributing to MarkdownViewr
Thanks for helping. This project follows the
[KofTwentyTwo standards](https://github.com/KofTwentyTwo/standards); this page is the
short version of what they ask of a contribution.
Questions, bug reports, documentation improvements, and code contributions are welcome. For everyday use, start with the [user guide](docs/user-guide.md). For build commands and tests, use [DEVELOPERS.md](DEVELOPERS.md).
## Before you start
For anything beyond a small fix, open an issue first so the approach can be agreed
before you invest time. Questions and proposals are welcome in
[Issues](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/issues).
For a substantial change, open an [issue](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/issues/new/choose) first to agree on the approach. Ask usage questions in [Discussions](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/discussions).
## Setup
Report security vulnerabilities privately according to [SECURITY.md](SECURITY.md). Do not include private documents, credentials, or personal information in reports, screenshots, or test fixtures.
Prerequisites: Windows 10 1809 or later, the .NET 10 SDK (`global.json` pins the feature
band), and optionally Visual Studio 2026 or Rider. The
[workstation setup](https://github.com/KofTwentyTwo/standards#set-up-your-machine)
installs everything, including the git hooks' tools. The commands below are exactly what
CI runs; run them before pushing.
## Development workflow
```powershell
dotnet restore MarkdownViewr.Windows.slnx -p:Platform=x64 --locked-mode
dotnet build MarkdownViewr.Windows.slnx -p:Platform=x64 -warnaserror
This Product follows the [KofTwentyTwo standards](https://github.com/KofTwentyTwo/standards).
# Unit tests, then the 100% line-coverage gate over every measured assembly
dotnet test MarkdownViewr.Windows.slnx -p:Platform=x64 --no-build --filter "FullyQualifiedName!~UiTests" --settings coverage.runsettings --collect:"XPlat Code Coverage" --results-directory TestResults
./build/Assert-Coverage.ps1 -ResultsDirectory TestResults
1. Branch from `main` using `<type>/<short-description>`, for example `fix/source-highlighting`.
2. Follow the [developer guide](DEVELOPERS.md), install both pre-commit and commit-msg hooks, and run the relevant quality gates.
3. Make signed, signed-off [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/): `git commit -S -s`.
4. Open a pull request targeting `main` with a Conventional Commit title. PRs are squash merged.
5. Resolve all review threads and required checks before merging.
# End-to-end UI tests: FlaUI launches the app (needs an interactive desktop)
dotnet test tests/MarkdownViewr.Windows.UiTests
## Pull request checklist
# Style gate (Kingsrook layout, analyzers, naming)
dotnet format MarkdownViewr.Windows.slnx --verify-no-changes --severity warn --no-restore
- Explain the problem, resulting behavior, and linked issues.
- Report the checks you actually ran and any limitations.
- Include screenshots for UI changes and update user documentation.
- Retain zero-warning builds, clean formatting, and 100% measured core line coverage.
- Add regression tests for fixes; exercise native and installed behavior where relevant.
- Justify dependency changes and commit regenerated locks.
- Update the [threat model](docs/security/threat-model.md) for attack-surface changes.
- Preserve original MIT notices and dependency credits.
- Disclose substantial AI assistance and include a `Co-Authored-By:` trailer naming the model.
# PowerShell tooling gate (install PSScriptAnalyzer 1.25.0 once)
Import-Module PSScriptAnalyzer -RequiredVersion 1.25.0
Invoke-ScriptAnalyzer -Path build -Recurse -Settings ./PSScriptAnalyzerSettings.psd1 -EnableExit
Import-Module Pester -RequiredVersion 5.7.1
Invoke-Pester -Path build/Assert-Coverage.Tests.ps1 -CI
```
Required CI covers build/coverage/style, reader and installed-update acceptance, parser fuzzing, tooling tests, dependency/secret/workflow scans, CodeQL, PR title, and DCO.
`dotnet format` (without `--verify-no-changes`) fixes layout, `var` use, usings, and
line endings. Two Kingsrook rules it cannot apply are checked in review: three blank
lines between members, and a header comment on every type and method (Rider and
ReSharper apply the blank lines from `.editorconfig`). Changing a package version means
updating `Directory.Packages.props` and committing the regenerated `packages.lock.json`
files (`dotnet restore MarkdownViewr.Windows.slnx -p:Platform=x64 --force-evaluate`).
Under the single-maintainer policy, `review / automated` also requires a genuine current-commit acknowledgement from the configured human maintainer. An agent cannot provide that acknowledgement. See [repository setup](docs/repository-setup.md).
Install the git hooks once per clone (the hook configuration comes from
[KofTwentyTwo/standards](https://github.com/KofTwentyTwo/standards/tree/main/configs/pre-commit)):
## Sign-off and license
```sh
pre-commit install --hook-type pre-commit --hook-type commit-msg
```
## Workflow
1. Branch from `main` with a short-lived topic branch named `<type>/<short-description>`,
for example `feat/export` or `fix/42-splash-never-dismisses`.
2. Commit with [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
(`feat: ...`, `fix(settings): ...`). Every commit is **signed** and **signed off**:
`git commit -S -s` (see *Sign-off* below).
3. Open a pull request against `main`. Its title must itself be a Conventional Commit
header: pull requests are **squash-merged**, so the title becomes the commit message on
`main`.
4. Every required check must pass, and every review thread must be resolved, before the
pull request merges.
## What makes a contribution acceptable
- The build has **zero warnings**, and the format gate is clean.
- New behavior has tests; a bug fix has a test that fails without the fix.
- Line coverage of `MarkdownViewr.Windows.Core` stays at **100%**. Logic goes in `MarkdownViewr.Windows.Core`; the
WinUI project holds views and framework plumbing only
([ADR-0001](docs/adr/0001-adopt-the-windows-desktop-reference-architecture.md)).
- User-visible changes update the docs in the same pull request; attack-surface changes
update the [threat model](docs/security/threat-model.md).
- No secrets, credentials, or personal data, ever.
- New dependencies are justified in the pull request description.
## Checks that gate a merge
| Check | What it verifies |
| --- | --- |
| `pr / title` | The PR title is a Conventional Commit header |
| `pr / dco` | Every commit is signed off |
| `pr / dependency-review` | Added dependencies have no known vulnerabilities and an allowed license |
| `security / secrets`, `security / sca`, `security / workflows` | No secrets, no vulnerable or malicious dependencies, safe workflows |
| `codeql / analyze (csharp)`, `codeql / analyze (actions)` | No high-severity static analysis findings |
| `ci / build-test` | Locked restore, zero-warning build, unit tests, 100% coverage gate |
| `ci / format` | `dotnet format --verify-no-changes --severity warn` |
| `ci / ui-tests` | FlaUI end-to-end tests of the app |
| `installed-updates` | Real stable/dev installers, upgrades, restart and published-output UI smoke |
| `powershell`, `javascript` | Tool lint, setup/coverage boundary tests, review tests and dependency signatures |
| `codeql / analyze (javascript-typescript)` | Static analysis of repository review tooling |
| `review / automated` (single maintainer) | Current-commit automated review acknowledged by the configured human maintainer |
Run `npm ci --ignore-scripts`, `npm run lint` and `npm test` for repository JavaScript.
Run PSScriptAnalyzer with `PSScriptAnalyzerSettings.psd1` and Pester against
`build/*.Tests.ps1`. New owners follow [repository setup](docs/repository-setup.md).
## Sign-off (Developer Certificate of Origin)
By signing off a commit you certify the
[Developer Certificate of Origin 1.1](https://developercertificate.org/): that you wrote
the change, or otherwise have the right to submit it under the project's license.
`git commit -s` appends the sign-off:
Signing off certifies the [Developer Certificate of Origin 1.1](https://developercertificate.org/): you wrote the contribution or have the right to submit it under the project's license. `git commit -s` adds:
```text
Signed-off-by: Your Name <your.email@example.com>
```
The name and email must match the commit author. To add missing sign-offs to a branch,
run `git rebase --signoff main` and force-push the branch.
## AI-assisted contributions
AI coding tools are welcome. You remain the author: you must understand, test, and be
able to explain every line you submit, and you sign it off as your own. Note substantial
AI assistance in the pull request description.
Include a Co-Authored-By trailer naming the model for significant AI contributions
(K22-AGENT-40 / K22-AI-02).
## License
By contributing, you agree that your contributions are licensed under the project's
[MIT License](LICENSE).
The sign-off must match the commit author. Contributions are licensed under the project's [MIT License](LICENSE). AI assistance does not remove your responsibility to understand, test, and explain the change.
+92
View File
@@ -0,0 +1,92 @@
# Developer Guide
For installation and everyday use, see the [README](README.md) and [user guide](docs/user-guide.md). Contribution requirements are in [CONTRIBUTING.md](CONTRIBUTING.md).
## Environment and structure
Use Windows 10 1809 or later, Microsoft Edge WebView2 Runtime, and the .NET 10 SDK selected by `global.json`. The current release target is Windows x64.
- `src/MarkdownViewr.Windows.Core/`: testable document, rendering, theme, and view-model logic.
- `src/MarkdownViewr.Windows/`: WinUI views, WebView2 integration, Windows adapters, and `Assets/Brand/`.
- `tests/`: xUnit core tests, native/published UI acceptance, and installed-update probes.
- `build/`: coverage, repository setup, packaging, signing, and lifecycle validation.
- `docs/`: product scope, ADRs, security evidence, and release runbooks.
Keep core logic independent of WinUI. Treat documents, renderer messages, resource URLs, and imported themes as untrusted; review the [threat model](docs/security/threat-model.md) before changing those boundaries.
## Restore, build, and run
Run from the repository root:
```powershell
dotnet restore MarkdownViewr.Windows.slnx -p:Platform=x64 --locked-mode
dotnet build MarkdownViewr.Windows.slnx -p:Platform=x64 -warnaserror
dotnet run --project src/MarkdownViewr.Windows -p:Platform=x64
```
To isolate preview data, set `MARKDOWNVIEWR_WINDOWS_DATA_DIR` for that process to a disposable absolute folder. Do not use another person's profile or interfere with an existing preview.
## Tests and quality gates
```powershell
dotnet test tests/MarkdownViewr.Windows.Core.Tests --settings coverage.runsettings --collect:"XPlat Code Coverage" --results-directory TestResults
./build/Assert-Coverage.ps1 -ResultsDirectory TestResults
dotnet format MarkdownViewr.Windows.slnx --verify-no-changes --severity warn --no-restore
npm ci --ignore-scripts
npm run lint
npm test
```
Core tests use xUnit and Coverlet; retain 100% measured core line coverage. Name tests `Method_Scenario_Expectation` and add meaningful regression coverage for fixes. Deterministic tests inject time and filesystem boundaries.
Native desktop tests use FlaUI/UIA3 and require an available interactive desktop:
```powershell
dotnet test tests/MarkdownViewr.Windows.UiTests
```
Reserve the desktop before running physical-input tests. Hosted CI also exercises published output and actual stable/dev installation, update/restart, registration, and coexistence. See [installed-update acceptance](docs/installed-updates.md). Synthetic unsigned fixtures do not establish production signing.
For PowerShell, use PSScriptAnalyzer 1.25.0 with `PSScriptAnalyzerSettings.psd1`, then Pester 5.7.1 against `build/*.Tests.ps1`. Reproduce the exact sequential analyzer partitions and negative fixtures in [scripts.yml](.github/workflows/scripts.yml); engine exceptions and rule findings are failures.
Run the [seeded parser campaigns](docs/security/fuzzing.md) when changing parser or activation boundaries. Never use private documents as a corpus.
## Style and dependencies
Follow `.editorconfig`: three-space C# indentation, Allman braces, file-scoped namespaces, PascalCase public members, and `_camelCase` private fields. Keep type/method header comments and three blank lines between members. YAML uses two spaces.
Package versions live in `Directory.Packages.props`. After a deliberate version change, regenerate and commit affected locks:
```powershell
dotnet restore MarkdownViewr.Windows.slnx -p:Platform=x64 --force-evaluate
pre-commit install --hook-type pre-commit --hook-type commit-msg
```
Use `dotnet format` without `--verify-no-changes` for supported formatting fixes; inspect the diff before committing.
## Releases and verification
Follow [RELEASING.md](docs/RELEASING.md), [signing](docs/release-signing.md), and [repository setup](docs/repository-setup.md). Stable tags use `v1.2.3`; prereleases such as `v1.2.3-beta.1` publish to the isolated development channel.
The protected release builder signs owned binaries before packaging, verifies distributed signatures, and generates final checksums and attestations. Never bypass signing failures or publish fixture packages.
After publication, download the actual assets and verify them:
```powershell
Get-FileHash ./markdownviewr-windows-stable-Setup.exe -Algorithm SHA256
gh attestation verify ./markdownviewr-windows-stable-Setup.exe --repo KofTwentyTwo/MarkdownViewr.Windows --signer-repo KofTwentyTwo/standards
Get-AuthenticodeSignature ./markdownviewr-windows-stable-Setup.exe | Format-List Status, SignerCertificate
signtool verify /pa /all /v ./markdownviewr-windows-stable-Setup.exe
```
Compare hashes with the published `SHA256SUMS`; confirm publisher, trust, and RFC3161 timestamp. Verify both packages and their shipped payloads using the release runbook.
## Branding
The AppKit BrandTool generates the vector and multi-size ICO assets:
```powershell
brandtool --out src/MarkdownViewr.Windows/Assets/Brand --start '#1A2B4D' --end '#0C1836' --glyph 'M 144 328 V 184 L 208 260 L 272 184 V 328 M 344 184 V 328 M 312 296 L 344 328 L 376 296 M 144 380 H 376' --stroke 28
```
Preserve original-author and dependency attribution in [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md) and the About dialog.
+1 -1
View File
@@ -54,7 +54,7 @@
commit as "+<sha>" on AssemblyInformationalVersion, which the About dialog and the
CLI's version output show as a short hash. -->
<PropertyGroup>
<VersionPrefix>0.1.0</VersionPrefix>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix>dev</VersionSuffix>
</PropertyGroup>
+2 -1
View File
@@ -21,7 +21,8 @@
<ItemGroup>
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="2.5.1" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.28000.2705" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageVersion Include="Markdig" Version="1.4.0" />
<PackageVersion Include="HtmlSanitizer" Version="9.2.1039" />
</ItemGroup>
<ItemGroup Label="Tests">
+44 -68
View File
@@ -1,95 +1,71 @@
# MarkdownViewr
# MarkdownViewr for Windows
> In development: a Windows counterpart to [Darin's markdownViewr](https://github.com/darinkelkhoff/markdownViewr), built with AppKit. The application currently shows the starter's greeting screen; Markdown viewing is not implemented yet. See [the feature plan](docs/PRODUCT.md) and [development tasks](docs/TODO.md). No Windows release is available yet.
Read Markdown comfortably, keep up with changes, and turn your notes into something worth sharing.
A Windows Markdown viewer with live reload, themes and document navigation.
MarkdownViewr is a dedicated Windows reader for project documentation, notes, and README files. Open a document, choose a look you like, and focus on the words.
**[Release downloads](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/releases) · [User guide](docs/user-guide.md) · [Ask a question](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/discussions)**
> The first Windows release is being prepared. There are no published Windows downloads yet; the Releases page will contain the stable installer and development previews when publication completes.
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/KofTwentyTwo/MarkdownViewr.Windows/badge)](https://scorecard.dev/viewer/?uri=github.com/KofTwentyTwo/MarkdownViewr.Windows)
[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/15417/badge)](https://www.bestpractices.dev/en/projects/15417)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/KofTwentyTwo/MarkdownViewr.Windows/badge)](https://scorecard.dev/viewer/?uri=github.com/KofTwentyTwo/MarkdownViewr.Windows)
| | |
| --- | --- |
| **Tier** | Product ([scope](https://github.com/KofTwentyTwo/standards/blob/main/policies/README.md#scope)) |
| **Standards** | Adopting [KofTwentyTwo standards](https://github.com/KofTwentyTwo/standards) (pre-v1.0); built from the KofTwentyTwo Windows app template |
| **Latest release** | [Releases](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/releases/latest) |
![MarkdownViewr displaying project notes with the GitHub Light palette](docs/images/reading-light.png)
## Install
## Make Markdown easier to read
Download `markdownviewr-windows-stable-Setup.exe` from the
[latest release](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/releases/latest) and run it. It
installs per user (no administrator rights needed) and updates itself in place from
**Help → Check for updates**. `markdownviewr-windows-stable-Portable.zip` is a portable build that
does not self-update.
- **Make it your own.** Choose from 20 document palettes, customize typography, and use a light, dark, or system app appearance.
- **Follow changes as they happen.** Saved edits refresh automatically. The footer shows the loaded file's modification time and flags an unsuccessful reload.
- **Navigate long documents.** Show Contents, jump between headings, collapse sections, and find text.
- **See how it is written.** Open the highlighted, read-only source alongside the document. The split starts at 50/50 and resizes by dragging.
- **Share a finished document.** Print from the toolbar or save a PDF with separate print styling and optional actions to open the result or its folder.
- **Stay in your workflow.** Open several documents, launch your configured editor, or open the current document's folder in Explorer.
Prerelease tags (`v1.2.3-beta.1`) publish to the `dev` channel
(`markdownviewr-windows-dev-Setup.exe`); an installation only ever updates within its own channel.
Contents and Source start hidden. Turn them on when needed, or change their new-window defaults in **File → Settings → General**.
## Usage
![Dark document palette with highlighted Markdown source in an equal split](docs/images/source-dark.png)
- **Greeting (example feature):** type a name and press **Greet**. The name is remembered
for next time. This is the template's example of a feature: logic and state in
`MarkdownViewr.Windows.Core`, a thin view in `MarkdownViewr.Windows`.
- **View:** choose the light, dark, or system theme; turn the startup splash and the
startup update check on or off. Choices are saved immediately.
- **Help → Activity log:** a live view of the app's log, with an errors-only filter and a
shortcut to the log folder.
- **Help → Check for updates** and **Help → About**.
## Download and install
Settings and logs live in `%LOCALAPPDATA%\markdownviewr-windows`. Set the data-folder environment
variable the app reports in its log (the app id in capitals, plus `_DATA_DIR`) to use a
different folder, for example a portable one.
The planned Windows packages support **Windows 10 version 1809 or later and Windows 11, x64**. Microsoft Edge WebView2 Runtime is required. No .NET SDK is needed to use a release.
## Building from source
| Edition | Choose it for | Package |
| --- | --- | --- |
| Stable | Everyday reading | `markdownviewr-windows-stable-Setup.exe` |
| Development preview | Trying upcoming changes | `markdownviewr-windows-dev-Setup.exe` |
| Portable | Running without installation | `markdownviewr-windows-stable-Portable.zip` |
Prerequisites:
Once available, download a package from [Releases](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/releases). Run Setup for a per-user installation without administrator rights, or extract the portable ZIP before launching the app.
- Windows 10 version 1809 or later, x64 or ARM64
- .NET 10 SDK (the feature band is pinned by `global.json`)
- Optionally Visual Studio 2026 (Windows App SDK workload) or Rider
Installed editions check for updates through **Help → Check for updates**. Stable and development editions keep separate settings and update channels, so they can coexist. Portable copies do not self-update.
```powershell
# Once after generation, create and commit the NuGet locks locally:
# dotnet restore MarkdownViewr.Windows.slnx -p:Platform=x64
dotnet restore MarkdownViewr.Windows.slnx -p:Platform=x64 --locked-mode
dotnet build MarkdownViewr.Windows.slnx -p:Platform=x64 -warnaserror
dotnet test MarkdownViewr.Windows.slnx -p:Platform=x64 --no-build --filter "FullyQualifiedName!~UiTests"
dotnet test tests/MarkdownViewr.Windows.UiTests --no-build # launches the app on the desktop
```
## Quick start
Package versions are declared in `Directory.Packages.props` and locked in each project's
`packages.lock.json`. [CONTRIBUTING.md](CONTRIBUTING.md) has the full local workflow,
including the coverage gate and the format check that CI runs.
1. Choose **File → Open** or press **Ctrl+O** and select your Markdown file.
2. Use **Theme** to choose a document palette, **Contents** to navigate, and **Source** to inspect the Markdown.
3. Save changes in your editor; the viewer refreshes automatically.
4. Choose **Print** in the toolbar to print or create a PDF.
## Verifying a release
To open Markdown files in the viewer by default, use an installed copy and choose **File → Settings → General → Choose default file types**. Select MarkdownViewr in Windows Default Apps for your preferred extensions. Windows asks you to make that choice; installation preserves existing defaults.
Every release asset is listed in `SHA256SUMS` and has a build-provenance attestation
signed by the KofTwentyTwo release workflow:
See the [user guide](docs/user-guide.md) for settings, shortcuts, printing, and troubleshooting.
```powershell
# Integrity: the file matches the published checksum
(Get-FileHash .\markdownviewr-windows-stable-Setup.exe -Algorithm SHA256).Hash # compare to SHA256SUMS
## Help and feedback
# Origin: built by the KofTwentyTwo release workflow from this repository
gh attestation verify .\markdownviewr-windows-stable-Setup.exe --repo KofTwentyTwo/MarkdownViewr.Windows --signer-repo KofTwentyTwo/standards
Use **Help → Community support** for questions and **Help → Submit an issue** for bugs or feature requests. You can also visit [Discussions](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/discussions) or [Issues](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/issues) directly.
# Publisher: Authenticode signature (once code signing is enabled; see SECURITY.md)
Get-AuthenticodeSignature .\markdownviewr-windows-stable-Setup.exe | Format-List Status, SignerCertificate
```
For a bug report, include the version from **Help → About**, your Windows version, and steps to reproduce. Remove private information from logs and screenshots. Report security vulnerabilities privately using [SECURITY.md](SECURITY.md).
## Support
## Developers and contributors
The latest minor release receives fixes; see [SECURITY.md](SECURITY.md#supported-versions)
for the full support policy.
Want to help? Start with [CONTRIBUTING.md](CONTRIBUTING.md). Build commands, tests, architecture, and release verification are in [DEVELOPERS.md](DEVELOPERS.md).
## Security
This is a Product-tier project adopting the [KofTwentyTwo standards](https://github.com/KofTwentyTwo/standards), built with AppKit and WinUI 3. OpenSSF enrollment is active; registration does not mean a passing badge has been earned.
Report vulnerabilities privately; see [SECURITY.md](SECURITY.md). The app's attack
surface is described in its [threat model](docs/security/threat-model.md).
## Credits and license
## Contributing
Based on [Darin Kelkhoff's original macOS markdownViewr](https://github.com/darinkelkhoff/markdownViewr). Visit [Darin's website](https://kelkhoff.com/). The Windows app also credits the open-source libraries it uses in **Help → About → Components**.
See [CONTRIBUTING.md](CONTRIBUTING.md).
## License
[MIT](LICENSE). Copyright (c) 2026 James Maes.
Free and open source under the [MIT License](LICENSE). Original-author and dependency notices are preserved in [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md).
+9 -4
View File
@@ -48,10 +48,15 @@ minor receives security fixes for 6 months.
## Code signing
Windows installers and binaries are not yet Authenticode-signed (KofTwentyTwo standards
exception EX-0002, until the signing identity is validated). Until then, verify downloads
with `SHA256SUMS` and `gh attestation verify` as described in the
[README](README.md#verifying-a-release).
Public Windows releases require Authenticode signing of the final shipped binaries
and installers. Publication fails closed until the approved Azure signing identity
and expected publisher, trust and timestamp are verified. No verified signed
MarkdownViewr release has been published yet; unsigned test fixtures are not releases.
Verify final signatures, `SHA256SUMS` and provenance attestations as described in the
[README](README.md#verifying-a-release) and
[release signing instructions](docs/release-signing.md). No historical exception
is claimed as permission to publish unsigned application releases.
## Published vulnerabilities
@@ -0,0 +1,27 @@
Copyright (C) Microsoft Corporation. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* The name of Microsoft Corporation, or the names of its contributors
may not be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,85 @@
NOTICES AND INFORMATION
Do Not Translate or Localize
This software incorporates material from third parties. Microsoft makes certain
open source code available at https://3rdpartysource.microsoft.com, or you may
send a check or money order for US $5.00, including the product name, the open
source component name, and version number, to:
Source Code Compliance Team
Microsoft Corporation
One Microsoft Way
Redmond, WA 98052
USA
Notwithstanding any other terms, you may reverse engineer this software to the
extent required to debug changes to any libraries licensed under the GNU Lesser
General Public License.
----------------------------------------------------------------
Antlr3.Runtime 3.5.2-rc1 - BSD 3-Clause
[The "BSD license"]
Copyright (c) 2011 The ANTLR Project
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------
---------------------------------------------------------
StringTemplate4 4.0.9-rc1 - BSD 3-Clause
[The "BSD license"]
Copyright (c) 2011 The ANTLR Project
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------
@@ -0,0 +1,51 @@
MICROSOFT SOFTWARE LICENSE TERMS
Microsoft Windows ML Runtime
________________________________________
IF YOU LIVE IN (OR ARE A BUSINESS WITH A PRINCIPAL PLACE OF BUSINESS IN) THE UNITED STATES, PLEASE READ THE “BINDING ARBITRATION AND CLASS ACTION WAIVER” SECTION BELOW. IT AFFECTS HOW DISPUTES ARE RESOLVED.
________________________________________
These license terms are an agreement between you and Microsoft Corporation (or one of its affiliates). They apply to the software named above and any Microsoft services or software updates (except to the extent such services or updates are accompanied by new or additional terms, in which case those different terms apply prospectively and do not alter your or Microsoft’s rights relating to pre-updated software or services). IF YOU COMPLY WITH THESE LICENSE TERMS, YOU HAVE THE RIGHTS BELOW. BY USING THE SOFTWARE, YOU ACCEPT THESE TERMS.
1. INSTALLATION AND USE RIGHTS.
a) General. Subject to the terms of this agreement, you may install and use any number of copies of the software to develop and test your applications, solely for use on Windows. When building Generative AI applications follow the guidelines in https://learn.microsoft.com/windows/ai/rai.
b) Included Microsoft Applications. The software may include other Microsoft applications. These license terms apply to those included applications, if any, unless other license terms are provided with the other Microsoft applications.
c) Microsoft Platforms. The software may include components from Microsoft Windows. These components are governed by separate agreements and their own product support policies, as described in the license terms found in the installation directory for that component or in the “Licenses” folder accompanying the software.
d) Third Party Materials. The software may use or include source code, object code, or data available under an open source or open data license (“Open Source Materials”) or a proprietary license from a third party (with Open Source Materials, collectively, “Third Party Materials”) that are licensed under the specific license terms applicable to the Third Party Materials (an “Third Party License Agreement”). To the extent that any Third Party License Agreement requires that the corresponding Third Party Materials be licensed under terms that are different from the terms set forth in this Agreement, then such Third Party Materials are licensed under the terms of that Third Party License Agreement and the other terms and conditions in this Agreement will not apply with respect to those Third Party Materials. You (a) acknowledge that any such Third Party License Agreement is solely between Licensee and the applicable licensor of the Third Party Materials, and (b) will comply with the terms of any such Third Party License Agreement. Without limitation to the foregoing, the licenses referenced on this website (https://learn.microsoft.com/en-us/windows/ai/new-windows-ml/supported-execution-providers) are deemed to be “Third Party Materials.”
2. DATA.
a) Data Collection. The software may collect information about you and your use of the software, and send that to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may opt-out of many of these scenarios, but not all, as described in the product documentation. There are also some features in the software that may enable you to collect data from users of your applications. If you use these features to enable data collection in your applications, you must comply with applicable law, including providing appropriate notices to users of your applications. You can learn more about data collection and use in the help documentation and the privacy statement at https://aka.ms/privacy. Your use of the software operates as your consent to these practices.
b) Processing of Personal Data. To the extent Microsoft is a processor or subprocessor of personal data in connection with the software, Microsoft makes the commitments in the European Union General Data Protection Regulation Terms of the Online Services Terms to all customers effective May 25, 2018, at https://docs.microsoft.com/en-us/legal/gdpr.
3. DISTRIBUTABLE CODE. The software may contain code you are permitted to distribute (i.e. make available for third parties) in applications you develop, as described in this Section.
a) Distribution Rights. The code and test files described below are distributable if included with the software.
i. Any files that are binplaced with your application by the WindowsAppSDK NuGet package are, by definition, permitted to be redistributed. This applies to both framework package dependent and self-contained deployments.
ii. Image Library. You may copy and distribute images, graphics, and animations in the Image Library as described in the software documentation; and
iii. Third Party Distribution. You may permit distributors of your applications to copy and distribute any of this distributable code you elect to distribute with your applications.
b) Distribution Requirements. For any code you distribute, you must:
i. add significant primary functionality to it in your applications;
ii. require distributors and external end users to agree to terms that protect it and Microsoft at least as much as this agreement; and
iii. indemnify, defend, and hold harmless Microsoft from any claims, including attorneys’ fees, related to the distribution or use of your applications, except to the extent that any claim is based solely on the unmodified distributable code.
c) Distribution Restrictions. You may not:
i. use Microsoft’s trademarks or trade dress in your application in any way that suggests your application comes from or is endorsed by Microsoft; or
ii. modify or distribute the source code of any distributable code so that any part of it becomes subject to any license that requires that the distributable code, any other part of the software, or any of Microsoft’s other intellectual property be disclosed or distributed in source code form, or that others have the right to modify it.
4. SCOPE OF LICENSE. The software is licensed, not sold. Microsoft reserves all other rights. Unless applicable law gives you more rights despite this limitation, you will not (and have no right to):
a) work around any technical limitations in the software that only allow you to use it in certain ways;
b) reverse engineer, decompile or disassemble the software, or otherwise attempt to derive the source code for the software, except and to the extent required by third party licensing terms governing use of certain open source components that may be included in the software;
c) remove, minimize, block, or modify any notices of Microsoft or its suppliers in the software;
d) use the software in any way that is against the law or to create or propagate malware; or
e) share, publish, distribute, or lease the software (except for any distributable code, subject to the terms above), provide the software as a stand-alone offering for others to use, or transfer the software or this agreement to any third party.
5. EXPORT RESTRICTIONS. You must comply with all domestic and international export laws and regulations that apply to the software, which include restrictions on destinations, end users, and end use. For further information on export restrictions, visit https://aka.ms/exporting.
6. SUPPORT SERVICES. Microsoft is not obligated under this agreement to provide any support services for the software. Any support provided is “as is”, “with all faults”, and without warranty of any kind.
7. UPDATES. The software may periodically check for updates, and download and install them for you. You may obtain updates only from Microsoft or authorized sources. Microsoft may need to update your system to provide you with updates. You agree to receive these automatic updates without any additional notice. Updates may not include or support all existing software features, services, or peripheral devices.
8. BINDING ARBITRATION AND CLASS ACTION WAIVER. This Section applies if you live in (or, if a business, your principal place of business is in) the United States. If you and Microsoft have a dispute, you and Microsoft agree to try for 60 days to resolve it informally. If you and Microsoft can’t, you and Microsoft agree to binding individual arbitration before the American Arbitration Association under the Federal Arbitration Act (“FAA”), and not to sue in court in front of a judge or jury. Instead, a neutral arbitrator will decide. Class action lawsuits, class-wide arbitrations, private attorney-general actions, and any other proceeding where someone acts in a representative capacity are not allowed; nor is combining individual proceedings without the consent of all parties. The complete Arbitration Agreement contains more terms and is at https://aka.ms/arb-agreement-4. You and Microsoft agree to these terms.
9. ENTIRE AGREEMENT. This agreement, and any other terms Microsoft may provide for supplements, updates, or third-party applications, is the entire agreement for the software.
10. APPLICABLE LAW AND PLACE TO RESOLVE DISPUTES. If you acquired the software in the United States or Canada, the laws of the state or province where you live (or, if a business, where your principal place of business is located) govern the interpretation of this agreement, claims for its breach, and all other claims (including consumer protection, unfair competition, and tort claims), regardless of conflict of laws principles, except that the FAA governs everything related to arbitration. If you acquired the software in any other country, its laws apply, except that the FAA governs everything related to arbitration. If U.S. federal jurisdiction exists, you and Microsoft consent to exclusive jurisdiction and venue in the federal court in King County, Washington for all disputes heard in court (excluding arbitration). If not, you and Microsoft consent to exclusive jurisdiction and venue in the Superior Court of King County, Washington for all disputes heard in court (excluding arbitration).
11. CONSUMER RIGHTS; REGIONAL VARIATIONS. This agreement describes certain legal rights. You may have other rights, including consumer rights, under the laws of your state or country. Separate and apart from your relationship with Microsoft, you may also have rights with respect to the party from which you acquired the software. This agreement does not change those other rights if the laws of your state or country do not permit it to do so. For example, if you acquired the software in one of the below regions, or mandatory country law applies, then the following provisions apply to you:
a) Australia. You have statutory guarantees under the Australian Consumer Law and nothing in this agreement is intended to affect those rights.
b) Canada. If you acquired this software in Canada, you may stop receiving updates by turning off the automatic update feature, disconnecting your device from the Internet (if and when you re-connect to the Internet, however, the software will resume checking for and installing updates), or uninstalling the software. The product documentation, if any, may also specify how to turn off updates for your specific device or software.
c) Germany and Austria.
i. Warranty. The properly licensed software will perform substantially as described in any Microsoft materials that accompany the software. However, Microsoft gives no contractual guarantee in relation to the licensed software.
ii. Limitation of Liability. In case of intentional conduct, gross negligence, claims based on the Product Liability Act, as well as, in case of death or personal or physical injury, Microsoft is liable according to the statutory law.
Subject to the foregoing clause ii., Microsoft will only be liable for slight negligence if Microsoft is in breach of such material contractual obligations, the fulfillment of which facilitate the due performance of this agreement, the breach of which would endanger the purpose of this agreement and the compliance with which a party may constantly trust in (so-called "cardinal obligations"). In other cases of slight negligence, Microsoft will not be liable for slight negligence.
12. DISCLAIMER OF WARRANTY. THE SOFTWARE IS LICENSED “AS IS.” YOU BEAR THE RISK OF USING IT. MICROSOFT GIVES NO EXPRESS WARRANTIES, GUARANTEES, OR CONDITIONS. TO THE EXTENT PERMITTED UNDER APPLICABLE LAWS, MICROSOFT EXCLUDES ALL IMPLIED WARRANTIES, INCLUDING MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
13. LIMITATION ON AND EXCLUSION OF DAMAGES. IF YOU HAVE ANY BASIS FOR RECOVERING DAMAGES DESPITE THE PRECEDING DISCLAIMER OF WARRANTY, YOU CAN RECOVER FROM MICROSOFT AND ITS SUPPLIERS ONLY DIRECT DAMAGES UP TO U.S. $5.00. YOU CANNOT RECOVER ANY OTHER DAMAGES, INCLUDING CONSEQUENTIAL, LOST PROFITS, SPECIAL, INDIRECT, OR INCIDENTAL DAMAGES.
This limitation applies to (a) anything related to the software, services, content (including code) on third party Internet sites, or third party applications; and (b) claims for breach of contract, warranty, guarantee, or condition; strict liability, negligence, or other tort; or any other claim; in each case to the extent permitted by applicable law.
It also applies even if Microsoft knew or should have known about the possibility of the damages. The above limitation or exclusion may not apply to you because your state, province, or country may not allow the exclusion or limitation of incidental, consequential, or other damages.
@@ -0,0 +1,92 @@
MICROSOFT SOFTWARE LICENSE TERMS
MICROSOFT WINDOWS APP SDK
________________________________________
IF YOU LIVE IN (OR ARE A BUSINESS WITH A PRINCIPAL PLACE OF BUSINESS IN) THE UNITED STATES, PLEASE READ THE “BINDING ARBITRATION AND CLASS ACTION WAIVER” SECTION BELOW. IT AFFECTS HOW DISPUTES ARE RESOLVED.
________________________________________
These license terms are an agreement between you and Microsoft Corporation (or one of its affiliates). They apply to the software named above and any Microsoft services or software updates (except to the extent such services or updates are accompanied by new or additional terms, in which case those different terms apply prospectively and do not alter your or Microsoft’s rights relating to pre-updated software or services). IF YOU COMPLY WITH THESE LICENSE TERMS, YOU HAVE THE RIGHTS BELOW. BY USING THE SOFTWARE, YOU ACCEPT THESE TERMS.
1. INSTALLATION AND USE RIGHTS.
a) General. Subject to the terms of this agreement, you may install and use any number of copies of the software to develop and test your applications, solely for use on Windows. When building Generative AI applications follow the guidelines in https://learn.microsoft.com/windows/ai/rai.
b) Included Microsoft Applications. The software may include other Microsoft applications. These license terms apply to those included applications, if any, unless other license terms are provided with the other Microsoft applications.
c) Microsoft Platforms. The software may include components from Microsoft Windows. These components are governed by separate agreements and their own product support policies, as described in the license terms found in the installation directory for that component or in the “Licenses” folder accompanying the software.
2. DATA.
a) Data Collection. The software may collect information about you and your use of the software, and send that to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may opt-out of many of these scenarios, but not all, as described in the product documentation. There are also some features in the software that may enable you to collect data from users of your applications. If you use these features to enable data collection in your applications, you must comply with applicable law, including providing appropriate notices to users of your applications. You can learn more about data collection and use in the help documentation and the privacy statement at https://aka.ms/privacy. Your use of the software operates as your consent to these practices.
b) Processing of Personal Data. To the extent Microsoft is a processor or subprocessor of personal data in connection with the software, Microsoft makes the commitments in the European Union General Data Protection Regulation Terms of the Online Services Terms to all customers effective May 25, 2018, at https://docs.microsoft.com/en-us/legal/gdpr.
3. DISTRIBUTABLE CODE. The software may contain code you are permitted to distribute (i.e. make available for third parties) in applications you develop, as described in this Section.
a) Distribution Rights. The code and test files described below are distributable if included with the software.
i. Any files that are binplaced with your application by the WindowsAppSDK NuGet package are, by definition, permitted to be redistributed. This applies to both framework package dependent and self-contained deployments.
ii. Image Library. You may copy and distribute images, graphics, and animations in the Image Library as described in the software documentation; and
iii. Third Party Distribution. You may permit distributors of your applications to copy and distribute any of this distributable code you elect to distribute with your applications.
b) Distribution Requirements. For any code you distribute, you must:
i. add significant primary functionality to it in your applications;
ii. require distributors and external end users to agree to terms that protect it and Microsoft at least as much as this agreement; and
iii. indemnify, defend, and hold harmless Microsoft from any claims, including attorneys’ fees, related to the distribution or use of your applications, except to the extent that any claim is based solely on the unmodified distributable code.
c) Distribution Restrictions. You may not:
i. use Microsoft’s trademarks or trade dress in your application in any way that suggests your application comes from or is endorsed by Microsoft; or
ii. modify or distribute the source code of any distributable code so that any part of it becomes subject to any license that requires that the distributable code, any other part of the software, or any of Microsoft’s other intellectual property be disclosed or distributed in source code form, or that others have the right to modify it.
4. SCOPE OF LICENSE. The software is licensed, not sold. Microsoft reserves all other rights. Unless applicable law gives you more rights despite this limitation, you will not (and have no right to):
a) work around any technical limitations in the software that only allow you to use it in certain ways;
b) reverse engineer, decompile or disassemble the software, or otherwise attempt to derive the source code for the software, except and to the extent required by third party licensing terms governing use of certain open source components that may be included in the software;
c) remove, minimize, block, or modify any notices of Microsoft or its suppliers in the software;
d) use the software in any way that is against the law or to create or propagate malware; or
e) share, publish, distribute, or lease the software (except for any distributable code, subject to the terms above), provide the software as a stand-alone offering for others to use, or transfer the software or this agreement to any third party.
5. EXPORT RESTRICTIONS. You must comply with all domestic and international export laws and regulations that apply to the software, which include restrictions on destinations, end users, and end use. For further information on export restrictions, visit https://aka.ms/exporting.
6. SUPPORT SERVICES. Microsoft is not obligated under this agreement to provide any support services for the software. Any support provided is “as is”, “with all faults”, and without warranty of any kind.
7. UPDATES. The software may periodically check for updates, and download and install them for you. You may obtain updates only from Microsoft or authorized sources. Microsoft may need to update your system to provide you with updates. You agree to receive these automatic updates without any additional notice. Updates may not include or support all existing software features, services, or peripheral devices.
8. BINDING ARBITRATION AND CLASS ACTION WAIVER. This Section applies if you live in (or, if a business, your principal place of business is in) the United States. If you and Microsoft have a dispute, you and Microsoft agree to try for 60 days to resolve it informally. If you and Microsoft can’t, you and Microsoft agree to binding individual arbitration before the American Arbitration Association under the Federal Arbitration Act (“FAA”), and not to sue in court in front of a judge or jury. Instead, a neutral arbitrator will decide. Class action lawsuits, class-wide arbitrations, private attorney-general actions, and any other proceeding where someone acts in a representative capacity are not allowed; nor is combining individual proceedings without the consent of all parties. The complete Arbitration Agreement contains more terms and is at https://aka.ms/arb-agreement-4. You and Microsoft agree to these terms.
9. ENTIRE AGREEMENT. This agreement, and any other terms Microsoft may provide for supplements, updates, or third-party applications, is the entire agreement for the software.
10. APPLICABLE LAW AND PLACE TO RESOLVE DISPUTES. If you acquired the software in the United States or Canada, the laws of the state or province where you live (or, if a business, where your principal place of business is located) govern the interpretation of this agreement, claims for its breach, and all other claims (including consumer protection, unfair competition, and tort claims), regardless of conflict of laws principles, except that the FAA governs everything related to arbitration. If you acquired the software in any other country, its laws apply, except that the FAA governs everything related to arbitration. If U.S. federal jurisdiction exists, you and Microsoft consent to exclusive jurisdiction and venue in the federal court in King County, Washington for all disputes heard in court (excluding arbitration). If not, you and Microsoft consent to exclusive jurisdiction and venue in the Superior Court of King County, Washington for all disputes heard in court (excluding arbitration).
11. CONSUMER RIGHTS; REGIONAL VARIATIONS. This agreement describes certain legal rights. You may have other rights, including consumer rights, under the laws of your state or country. Separate and apart from your relationship with Microsoft, you may also have rights with respect to the party from which you acquired the software. This agreement does not change those other rights if the laws of your state or country do not permit it to do so. For example, if you acquired the software in one of the below regions, or mandatory country law applies, then the following provisions apply to you:
a) Australia. You have statutory guarantees under the Australian Consumer Law and nothing in this agreement is intended to affect those rights.
b) Canada. If you acquired this software in Canada, you may stop receiving updates by turning off the automatic update feature, disconnecting your device from the Internet (if and when you re-connect to the Internet, however, the software will resume checking for and installing updates), or uninstalling the software. The product documentation, if any, may also specify how to turn off updates for your specific device or software.
c) Germany and Austria.
i. Warranty. The properly licensed software will perform substantially as described in any Microsoft materials that accompany the software. However, Microsoft gives no contractual guarantee in relation to the licensed software.
ii. Limitation of Liability. In case of intentional conduct, gross negligence, claims based on the Product Liability Act, as well as, in case of death or personal or physical injury, Microsoft is liable according to the statutory law.
Subject to the foregoing clause ii., Microsoft will only be liable for slight negligence if Microsoft is in breach of such material contractual obligations, the fulfillment of which facilitate the due performance of this agreement, the breach of which would endanger the purpose of this agreement and the compliance with which a party may constantly trust in (so-called "cardinal obligations"). In other cases of slight negligence, Microsoft will not be liable for slight negligence.
12. DISCLAIMER OF WARRANTY. THE SOFTWARE IS LICENSED “AS IS.” YOU BEAR THE RISK OF USING IT. MICROSOFT GIVES NO EXPRESS WARRANTIES, GUARANTEES, OR CONDITIONS. TO THE EXTENT PERMITTED UNDER APPLICABLE LAWS, MICROSOFT EXCLUDES ALL IMPLIED WARRANTIES, INCLUDING MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
13. LIMITATION ON AND EXCLUSION OF DAMAGES. IF YOU HAVE ANY BASIS FOR RECOVERING DAMAGES DESPITE THE PRECEDING DISCLAIMER OF WARRANTY, YOU CAN RECOVER FROM MICROSOFT AND ITS SUPPLIERS ONLY DIRECT DAMAGES UP TO U.S. $5.00. YOU CANNOT RECOVER ANY OTHER DAMAGES, INCLUDING CONSEQUENTIAL, LOST PROFITS, SPECIAL, INDIRECT, OR INCIDENTAL DAMAGES.
This limitation applies to (a) anything related to the software, services, content (including code) on third party Internet sites, or third party applications; and (b) claims for breach of contract, warranty, guarantee, or condition; strict liability, negligence, or other tort; or any other claim; in each case to the extent permitted by applicable law.
It also applies even if Microsoft knew or should have known about the possibility of the damages. The above limitation or exclusion may not apply to you because your state, province, or country may not allow the exclusion or limitation of incidental, consequential, or other damages.
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,53 @@
MICROSOFT SOFTWARE LICENSE TERMS
1. MICROSOFT WINDOWS APP SDK
2. WINDOWS MACHINE LEARNING (ML)
________________________________________
IF YOU LIVE IN (OR ARE A BUSINESS WITH A PRINCIPAL PLACE OF BUSINESS IN) THE UNITED STATES, PLEASE READ THE “BINDING ARBITRATION AND CLASS ACTION WAIVER” SECTION BELOW. IT AFFECTS HOW DISPUTES ARE RESOLVED.
________________________________________
These license terms are an agreement between you and Microsoft Corporation (or one of its affiliates). They apply to the software named above and any Microsoft services or software updates (except to the extent such services or updates are accompanied by new or additional terms, in which case those different terms apply prospectively and do not alter your or Microsoft’s rights relating to pre-updated software or services). IF YOU COMPLY WITH THESE LICENSE TERMS, YOU HAVE THE RIGHTS BELOW. BY USING THE SOFTWARE, YOU ACCEPT THESE TERMS.
1. INSTALLATION AND USE RIGHTS.
a) General. Subject to the terms of this agreement, you may install and use any number of copies of the software to develop and test your applications, solely for use on Windows. When building Generative AI applications follow the guidelines in https://learn.microsoft.com/windows/ai/rai.
b) Included Microsoft Applications. The software may include other Microsoft applications. These license terms apply to those included applications, if any, unless other license terms are provided with the other Microsoft applications.
c) Microsoft Platforms. The software may include components from Microsoft Windows. These components are governed by separate agreements and their own product support policies, as described in the license terms found in the installation directory for that component or in the “Licenses” folder accompanying the software.
d) Third Party Materials. The software may use or include source code, object code, or data available under an open source or open data license (“Open Source Materials”) or a proprietary license from a third party (with Open Source Materials, collectively, “Third Party Materials”) that are licensed under the specific license terms applicable to the Third Party Materials (an “Third Party License Agreement”). To the extent that any Third Party License Agreement requires that the corresponding Third Party Materials be licensed under terms that are different from the terms set forth in this Agreement, then such Third Party Materials are licensed under the terms of that Third Party License Agreement and the other terms and conditions in this Agreement will not apply with respect to those Third Party Materials. You (a) acknowledge that any such Third Party License Agreement is solely between Licensee and the applicable licensor of the Third Party Materials, and (b) will comply with the terms of any such Third Party License Agreement. Without limitation to the foregoing, the licenses referenced on this website (https://learn.microsoft.com/en-us/windows/ai/new-windows-ml/supported-execution-providers) are deemed to be “Third Party Materials.”
2. DATA.
a) Data Collection. The software may collect information about you and your use of the software, and send that to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may opt-out of many of these scenarios, but not all, as described in the product documentation. There are also some features in the software that may enable you to collect data from users of your applications. If you use these features to enable data collection in your applications, you must comply with applicable law, including providing appropriate notices to users of your applications. You can learn more about data collection and use in the help documentation and the privacy statement at https://aka.ms/privacy. Your use of the software operates as your consent to these practices.
b) Processing of Personal Data. To the extent Microsoft is a processor or subprocessor of personal data in connection with the software, Microsoft makes the commitments in the European Union General Data Protection Regulation Terms of the Online Services Terms to all customers effective May 25, 2018, at https://docs.microsoft.com/en-us/legal/gdpr.
3. DISTRIBUTABLE CODE. The software may contain code you are permitted to distribute (i.e. make available for third parties) in applications you develop, as described in this Section.
a) Distribution Rights. The code and test files described below are distributable if included with the software.
i. Any files that are binplaced with your application by the WindowsAppSDK NuGet package are, by definition, permitted to be redistributed. This applies to both framework package dependent and self-contained deployments.
ii. Image Library. You may copy and distribute images, graphics, and animations in the Image Library as described in the software documentation; and
iii. Third Party Distribution. You may permit distributors of your applications to copy and distribute any of this distributable code you elect to distribute with your applications.
b) Distribution Requirements. For any code you distribute, you must:
i. add significant primary functionality to it in your applications;
ii. require distributors and external end users to agree to terms that protect it and Microsoft at least as much as this agreement; and
iii. indemnify, defend, and hold harmless Microsoft from any claims, including attorneys’ fees, related to the distribution or use of your applications, except to the extent that any claim is based solely on the unmodified distributable code.
c) Distribution Restrictions. You may not:
i. use Microsoft’s trademarks or trade dress in your application in any way that suggests your application comes from or is endorsed by Microsoft; or
ii. modify or distribute the source code of any distributable code so that any part of it becomes subject to any license that requires that the distributable code, any other part of the software, or any of Microsoft’s other intellectual property be disclosed or distributed in source code form, or that others have the right to modify it.
4. SCOPE OF LICENSE. The software is licensed, not sold. Microsoft reserves all other rights. Unless applicable law gives you more rights despite this limitation, you will not (and have no right to):
a) work around any technical limitations in the software that only allow you to use it in certain ways;
b) reverse engineer, decompile or disassemble the software, or otherwise attempt to derive the source code for the software, except and to the extent required by third party licensing terms governing use of certain open source components that may be included in the software;
c) remove, minimize, block, or modify any notices of Microsoft or its suppliers in the software;
d) use the software in any way that is against the law or to create or propagate malware; or
e) share, publish, distribute, or lease the software (except for any distributable code, subject to the terms above), provide the software as a stand-alone offering for others to use, or transfer the software or this agreement to any third party.
5. EXPORT RESTRICTIONS. You must comply with all domestic and international export laws and regulations that apply to the software, which include restrictions on destinations, end users, and end use. For further information on export restrictions, visit https://aka.ms/exporting.
6. SUPPORT SERVICES. Microsoft is not obligated under this agreement to provide any support services for the software. Any support provided is “as is”, “with all faults”, and without warranty of any kind.
7. UPDATES. The software may periodically check for updates, and download and install them for you. You may obtain updates only from Microsoft or authorized sources. Microsoft may need to update your system to provide you with updates. You agree to receive these automatic updates without any additional notice. Updates may not include or support all existing software features, services, or peripheral devices.
8. BINDING ARBITRATION AND CLASS ACTION WAIVER. This Section applies if you live in (or, if a business, your principal place of business is in) the United States. If you and Microsoft have a dispute, you and Microsoft agree to try for 60 days to resolve it informally. If you and Microsoft can’t, you and Microsoft agree to binding individual arbitration before the American Arbitration Association under the Federal Arbitration Act (“FAA”), and not to sue in court in front of a judge or jury. Instead, a neutral arbitrator will decide. Class action lawsuits, class-wide arbitrations, private attorney-general actions, and any other proceeding where someone acts in a representative capacity are not allowed; nor is combining individual proceedings without the consent of all parties. The complete Arbitration Agreement contains more terms and is at https://aka.ms/arb-agreement-4. You and Microsoft agree to these terms.
9. ENTIRE AGREEMENT. This agreement, and any other terms Microsoft may provide for supplements, updates, or third-party applications, is the entire agreement for the software.
10. APPLICABLE LAW AND PLACE TO RESOLVE DISPUTES. If you acquired the software in the United States or Canada, the laws of the state or province where you live (or, if a business, where your principal place of business is located) govern the interpretation of this agreement, claims for its breach, and all other claims (including consumer protection, unfair competition, and tort claims), regardless of conflict of laws principles, except that the FAA governs everything related to arbitration. If you acquired the software in any other country, its laws apply, except that the FAA governs everything related to arbitration. If U.S. federal jurisdiction exists, you and Microsoft consent to exclusive jurisdiction and venue in the federal court in King County, Washington for all disputes heard in court (excluding arbitration). If not, you and Microsoft consent to exclusive jurisdiction and venue in the Superior Court of King County, Washington for all disputes heard in court (excluding arbitration).
11. CONSUMER RIGHTS; REGIONAL VARIATIONS. This agreement describes certain legal rights. You may have other rights, including consumer rights, under the laws of your state or country. Separate and apart from your relationship with Microsoft, you may also have rights with respect to the party from which you acquired the software. This agreement does not change those other rights if the laws of your state or country do not permit it to do so. For example, if you acquired the software in one of the below regions, or mandatory country law applies, then the following provisions apply to you:
a) Australia. You have statutory guarantees under the Australian Consumer Law and nothing in this agreement is intended to affect those rights.
b) Canada. If you acquired this software in Canada, you may stop receiving updates by turning off the automatic update feature, disconnecting your device from the Internet (if and when you re-connect to the Internet, however, the software will resume checking for and installing updates), or uninstalling the software. The product documentation, if any, may also specify how to turn off updates for your specific device or software.
c) Germany and Austria.
i. Warranty. The properly licensed software will perform substantially as described in any Microsoft materials that accompany the software. However, Microsoft gives no contractual guarantee in relation to the licensed software.
ii. Limitation of Liability. In case of intentional conduct, gross negligence, claims based on the Product Liability Act, as well as, in case of death or personal or physical injury, Microsoft is liable according to the statutory law.
Subject to the foregoing clause ii., Microsoft will only be liable for slight negligence if Microsoft is in breach of such material contractual obligations, the fulfillment of which facilitate the due performance of this agreement, the breach of which would endanger the purpose of this agreement and the compliance with which a party may constantly trust in (so-called "cardinal obligations"). In other cases of slight negligence, Microsoft will not be liable for slight negligence.
12. DISCLAIMER OF WARRANTY. THE SOFTWARE IS LICENSED “AS IS.” YOU BEAR THE RISK OF USING IT. MICROSOFT GIVES NO EXPRESS WARRANTIES, GUARANTEES, OR CONDITIONS. TO THE EXTENT PERMITTED UNDER APPLICABLE LAWS, MICROSOFT EXCLUDES ALL IMPLIED WARRANTIES, INCLUDING MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
13. LIMITATION ON AND EXCLUSION OF DAMAGES. IF YOU HAVE ANY BASIS FOR RECOVERING DAMAGES DESPITE THE PRECEDING DISCLAIMER OF WARRANTY, YOU CAN RECOVER FROM MICROSOFT AND ITS SUPPLIERS ONLY DIRECT DAMAGES UP TO U.S. $5.00. YOU CANNOT RECOVER ANY OTHER DAMAGES, INCLUDING CONSEQUENTIAL, LOST PROFITS, SPECIAL, INDIRECT, OR INCIDENTAL DAMAGES.
This limitation applies to (a) anything related to the software, services, content (including code) on third party Internet sites, or third party applications; and (b) claims for breach of contract, warranty, guarantee, or condition; strict liability, negligence, or other tort; or any other claim; in each case to the extent permitted by applicable law.
It also applies even if Microsoft knew or should have known about the possibility of the damages. The above limitation or exclusion may not apply to you because your state, province, or country may not allow the exclusion or limitation of incidental, consequential, or other damages.
@@ -0,0 +1,51 @@
NOTICES AND INFORMATION
Do Not Translate or Localize
This software incorporates material from third parties.
Microsoft makes certain open source code available at https://3rdpartysource.microsoft.com,
or you may send a check or money order for US $5.00, including the product name,
the open source component name, platform, and version number, to:
Source Code Compliance Team
Microsoft Corporation
One Microsoft Way
Redmond, WA 98052
USA
Notwithstanding any other terms, you may reverse engineer this software to the extent
required to debug changes to any libraries licensed under the GNU Lesser General Public License.
---------------------------------------------------------
Newtonsoft.Json 13.0.1 - MIT
Copyright James Newton-King 2008
Copyright (c) 2007 James Newton-King
Copyright (c) James Newton-King 2008
Copyright James Newton-King 2008 Json.NET
The MIT License (MIT)
Copyright (c) 2007 James Newton-King
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---------------------------------------------------------
@@ -0,0 +1,19 @@
NOTICES AND INFORMATION
Do Not Translate or Localize
This software incorporates material from third parties. Microsoft makes certain
open source code available at https://3rdpartysource.microsoft.com, or you may
send a check or money order for US $5.00, including the product name, the open
source component name, and version number, to:
Source Code Compliance Team
Microsoft Corporation
One Microsoft Way
Redmond, WA 98052
USA
Notwithstanding any other terms, you may reverse engineer this software to the
extent required to debug changes to any libraries licensed under the GNU Lesser
General Public License.
-----
@@ -0,0 +1,23 @@
The MIT License (MIT)
Copyright (c) .NET Foundation and Contributors
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
File diff suppressed because it is too large. Load diff
+254
View File
@@ -0,0 +1,254 @@
# Third-party notices
These notices accompany the libraries and platform components used by MarkdownViewr.
## Markdig 1.4.0
Source: <https://github.com/xoofx/markdig>
Copyright (c) 2016-2026, Alexandre Mutel
All rights reserved.
Redistribution and use in source and binary forms, with or without modification
, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## HtmlSanitizer 9.2.1039
Source: <https://github.com/mganss/HtmlSanitizer>
The MIT License (MIT)
Copyright (c) 2013-2016 Michael Ganss and HtmlSanitizer contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## AngleSharp 1.7.2
Source: <https://github.com/AngleSharp/AngleSharp>
The MIT License (MIT)
Copyright (c) 2013 - 2026 AngleSharp
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## AngleSharp.Css 1.0.2
Source: <https://github.com/AngleSharp/AngleSharp.Css>
The MIT License (MIT)
Copyright (c) 2013 - 2026 AngleSharp
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## markdownViewr resources, frontmatter and navigation behavior
This Windows application is based on **Darin Kelkhoff's macOS markdownViewr**.
Original project: [darinkelkhoff/markdownViewr](https://github.com/darinkelkhoff/markdownViewr).
Author website: [kelkhoff.com](https://kelkhoff.com/). Windows issues and support
belong to the Windows repository; this attribution does not imply endorsement.
Per-window reading zoom bounds/steps and native minus/percentage/plus behavior are
adapted from ContentView.swift and MarkdownViewrApp.swift. Font-size scaling is
adapted from ThemeManager.swift; original theme JSON and line height stay unchanged.
Frontmatter delimiter/metadata display behavior is adapted from MarkdownDocument.swift at the same pinned commit. Raw-HTML highlight/superscript/underline behavior and the metadata/body subscript boundary are also adapted from that file. Windows applies these patterns only to tokenizer-identified visible text, preserving attributes, comments, authored code and original source positions before passive sanitation. Named editor identities, list ordering/removal and file/folder opening semantics are adapted from EditorConfig.swift there; Windows uses bounded .exe choices and one literal process argument. Vim sequence/navigation, nested source folding and heading-anchor scroll interpolation semantics are adapted from Resources/template.html there, with native Windows Ctrl+F, literal source controls and input focus guards. The 20 original JSON themes are copied unchanged from Darin Kelkhoff's markdownViewr at commit 6d08bc9d27d7d7b2c2fa95bb282da330b8cf9021.
Source: <https://github.com/darinkelkhoff/markdownViewr/tree/6d08bc9d27d7d7b2c2fa95bb282da330b8cf9021/markdownViewr/Resources/themes>
MIT License
Copyright (c) 2026 Darin Kelkhoff
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
The print defaults, Clean Printing palette and print-media layout rules in
`Core/Printing/` and the native print adapter are adapted from Darin Kelkhoff's
MIT-licensed markdownViewr (`6d08bc9`), with original/adaptation copyright headers
retained. Actual PDF acceptance uses the built-in Windows PDF API; no additional
PDF renderer/parser package is shipped.
## Complete application and tooling inventory
About → Components links each direct/transitive application dependency.
Development/test tools are acknowledged separately and are not described as
shipped application libraries. Microsoft SDK packages include their own binary
license terms; they are not all labeled MIT. Inventory below was checked against
restored package assets and packaged license metadata.
| Component | Version | Scope | Project | License |
| --- | --- | --- | --- | --- |
| AngleSharp.Css | 1.0.2 | Application | [Project](https://github.com/AngleSharp/AngleSharp.Css) | [MIT](https://licenses.nuget.org/MIT) |
| AngleSharp | 1.7.2 | Application | [Project](https://github.com/AngleSharp/AngleSharp) | [MIT](https://licenses.nuget.org/MIT) |
| HtmlSanitizer | 9.2.1039 | Application | [Project](https://github.com/mganss/HtmlSanitizer) | [MIT](https://licenses.nuget.org/MIT) |
| KofTwentyTwo.AppKit.Updates | 0.1.0 | Application | [Project](https://github.com/KofTwentyTwo/AppKit) | [MIT](https://licenses.nuget.org/MIT) |
| KofTwentyTwo.AppKit.WinUI | 0.1.0 | Application | [Project](https://github.com/KofTwentyTwo/AppKit) | [MIT](https://licenses.nuget.org/MIT) |
| KofTwentyTwo.AppKit | 0.1.0 | Application | [Project](https://github.com/KofTwentyTwo/AppKit) | [MIT](https://licenses.nuget.org/MIT) |
| Markdig | 1.4.0 | Application | [Project](https://github.com/xoofx/markdig) | [BSD-2-Clause](https://licenses.nuget.org/BSD-2-Clause) |
| Meziantou.Analyzer | 3.0.296 | Development | [Project](https://github.com/meziantou/Meziantou.Analyzer) | [MIT](https://licenses.nuget.org/MIT) |
| Microsoft.Web.WebView2 | 1.0.3719.77 | Application | [Project](https://aka.ms/webview) | [Microsoft package license](https://www.nuget.org/packages/Microsoft.Web.WebView2/1.0.3719.77/License) |
| Microsoft.Windows.AI.MachineLearning | 2.1.74 | Application | [Project](https://github.com/microsoft/windowsappsdk) | [Microsoft package license](https://www.nuget.org/packages/Microsoft.Windows.AI.MachineLearning/2.1.74/License) |
| Microsoft.Windows.SDK.BuildTools.MSIX | 1.7.251221100 | Development | [Project](https://aka.ms/WinSDKProjectURL) | [Microsoft package license](https://www.nuget.org/packages/Microsoft.Windows.SDK.BuildTools.MSIX/1.7.251221100/License) |
| Microsoft.Windows.SDK.BuildTools | 10.0.28000.2705 | Development | [Project](https://aka.ms/WinSDKProjectURL) | [Package license](https://www.nuget.org/packages/Microsoft.Windows.SDK.BuildTools/10.0.28000.2705/License) |
| Microsoft.WindowsAppSDK.AI | 2.5.5 | Application | [Project](https://github.com/microsoft/windowsappsdk) | [Microsoft package license](https://www.nuget.org/packages/Microsoft.WindowsAppSDK.AI/2.5.5/License) |
| Microsoft.WindowsAppSDK.Base | 2.0.4 | Application | [Project](https://github.com/microsoft/windowsappsdk) | [Microsoft package license](https://www.nuget.org/packages/Microsoft.WindowsAppSDK.Base/2.0.4/License) |
| Microsoft.WindowsAppSDK.DWrite | 2.1.0 | Application | [Project](https://aka.ms/WindowsAppSDK) | [Microsoft package license](https://www.nuget.org/packages/Microsoft.WindowsAppSDK.DWrite/2.1.0/License) |
| Microsoft.WindowsAppSDK.Foundation | 2.3.12 | Application | [Project](https://aka.ms/windowsappsdk) | [Microsoft package license](https://www.nuget.org/packages/Microsoft.WindowsAppSDK.Foundation/2.3.12/License) |
| Microsoft.WindowsAppSDK.InteractiveExperiences | 2.1.9 | Application | [Project](https://aka.ms/windowsappsdk) | [Microsoft package license](https://www.nuget.org/packages/Microsoft.WindowsAppSDK.InteractiveExperiences/2.1.9/License) |
| Microsoft.WindowsAppSDK.ML | 2.1.94 | Application | [Project](https://github.com/microsoft/windowsml) | [Microsoft package license](https://www.nuget.org/packages/Microsoft.WindowsAppSDK.ML/2.1.94/License) |
| Microsoft.WindowsAppSDK.Runtime | 2.5.1 | Application | [Project](https://github.com/microsoft/windowsappsdk) | [Microsoft package license](https://www.nuget.org/packages/Microsoft.WindowsAppSDK.Runtime/2.5.1/License) |
| Microsoft.WindowsAppSDK.Search | 2.5.5 | Application | [Project](https://github.com/microsoft/windowsappsdk) | [Microsoft package license](https://www.nuget.org/packages/Microsoft.WindowsAppSDK.Search/2.5.5/License) |
| Microsoft.WindowsAppSDK.Widgets | 2.0.5 | Application | [Project](https://github.com/microsoft/windowsappsdk) | [Microsoft package license](https://www.nuget.org/packages/Microsoft.WindowsAppSDK.Widgets/2.0.5/License) |
| Microsoft.WindowsAppSDK.WinUI | 2.3.9 | Application | [Project](https://github.com/microsoft/windowsappsdk) | [Microsoft package license](https://www.nuget.org/packages/Microsoft.WindowsAppSDK.WinUI/2.3.9/License) |
| Microsoft.WindowsAppSDK | 2.5.1 | Application | [Project](https://github.com/microsoft/windowsappsdk) | [Microsoft package license](https://www.nuget.org/packages/Microsoft.WindowsAppSDK/2.5.1/License) |
| System.Numerics.Tensors | 9.0.0 | Application | [Project](https://github.com/dotnet/runtime) | [MIT](https://licenses.nuget.org/MIT) |
| Velopack | 1.2.161 | Application | [Project](https://github.com/velopack/velopack) | [MIT](https://licenses.nuget.org/MIT) |
| Microsoft.CodeCoverage | 18.10.1 | Development | [Project](https://github.com/microsoft/vstest) | [MIT](https://licenses.nuget.org/MIT) |
| Microsoft.NET.Test.Sdk | 18.10.1 | Development | [Project](https://github.com/microsoft/vstest) | [MIT](https://licenses.nuget.org/MIT) |
| Microsoft.TestPlatform.ObjectModel | 18.10.1 | Development | [Project](https://github.com/microsoft/vstest) | [MIT](https://licenses.nuget.org/MIT) |
| Microsoft.TestPlatform.TestHost | 18.10.1 | Development | [Project](https://github.com/microsoft/vstest) | [MIT](https://licenses.nuget.org/MIT) |
| coverlet.collector | 10.1.0 | Development | [Project](https://github.com/coverlet-coverage/coverlet) | [MIT](https://licenses.nuget.org/MIT) |
| xunit.abstractions | 2.0.3 | Development | [Project](https://github.com/xunit/xunit) | [Package license](https://www.nuget.org/packages/xunit.abstractions/2.0.3/License) |
| xunit.analyzers | 1.18.0 | Development | [Project](https://github.com/xunit/xunit.analyzers) | [Apache-2.0](https://licenses.nuget.org/Apache-2.0) |
| xunit.assert | 2.9.3 | Development | [Project](https://github.com/xunit/xunit) | [Apache-2.0](https://licenses.nuget.org/Apache-2.0) |
| xunit.core | 2.9.3 | Development | [Project](https://github.com/xunit/xunit) | [Apache-2.0](https://licenses.nuget.org/Apache-2.0) |
| xunit.extensibility.core | 2.9.3 | Development | [Project](https://github.com/xunit/xunit) | [Apache-2.0](https://licenses.nuget.org/Apache-2.0) |
| xunit.extensibility.execution | 2.9.3 | Development | [Project](https://github.com/xunit/xunit) | [Apache-2.0](https://licenses.nuget.org/Apache-2.0) |
| xunit.runner.visualstudio | 4.0.0 | Development | [Project](https://github.com/xunit/visualstudio.xunit) | [Apache-2.0](https://licenses.nuget.org/Apache-2.0) |
| xunit | 2.9.3 | Development | [Project](https://github.com/xunit/xunit) | [Apache-2.0](https://licenses.nuget.org/Apache-2.0) |
| FlaUI.Core | 5.0.0 | Development | [Project](https://github.com/FlaUI/FlaUI) | [MIT](https://www.nuget.org/packages/FlaUI.Core/5.0.0/License) |
| FlaUI.UIA3 | 5.0.0 | Development | [Project](https://github.com/FlaUI/FlaUI) | [MIT](https://www.nuget.org/packages/FlaUI.UIA3/5.0.0/License) |
| Interop.UIAutomationClient | 10.19041.0 | Development | [Project](https://github.com/Roemer/UIAutomation-Interop) | [MIT](https://www.nuget.org/packages/Interop.UIAutomationClient/10.19041.0/License) |
| Microsoft.Win32.SystemEvents | 8.0.0 | Development | [Project](https://github.com/dotnet/runtime) | [MIT](https://licenses.nuget.org/MIT) |
| System.CodeDom | 8.0.0 | Development | [Project](https://github.com/dotnet/runtime) | [MIT](https://licenses.nuget.org/MIT) |
| System.Configuration.ConfigurationManager | 8.0.1 | Development | [Project](https://github.com/dotnet/runtime) | [MIT](https://licenses.nuget.org/MIT) |
| System.Diagnostics.EventLog | 8.0.1 | Development | [Project](https://github.com/dotnet/runtime) | [MIT](https://licenses.nuget.org/MIT) |
| System.Diagnostics.PerformanceCounter | 8.0.1 | Development | [Project](https://github.com/dotnet/runtime) | [MIT](https://licenses.nuget.org/MIT) |
| System.Drawing.Common | 8.0.10 | Development | [Project](https://github.com/dotnet/winforms) | [MIT](https://licenses.nuget.org/MIT) |
| System.Management | 8.0.0 | Development | [Project](https://github.com/dotnet/runtime) | [MIT](https://licenses.nuget.org/MIT) |
| System.Security.Cryptography.ProtectedData | 8.0.0 | Development | [Project](https://github.com/dotnet/runtime) | [MIT](https://licenses.nuget.org/MIT) |
| System.Security.Permissions | 8.0.0 | Development | [Project](https://github.com/dotnet/runtime) | [MIT](https://licenses.nuget.org/MIT) |
| System.Windows.Extensions | 8.0.0 | Development | [Project](https://github.com/dotnet/runtime) | [MIT](https://licenses.nuget.org/MIT) |
| .NET Runtime | 10 | Application platform | [Project](https://github.com/dotnet/runtime) | [MIT](https://github.com/dotnet/runtime/blob/main/LICENSE.TXT), [third-party notices](https://github.com/dotnet/runtime/blob/main/THIRD-PARTY-NOTICES.TXT) |
Additional packaged license and notice files are retained verbatim under
`THIRD-PARTY-LICENSES/` and copied to `ThirdPartyLicenses/` in application output.
Identical texts shared by multiple SDK packages are stored once.
- Microsoft.Web.WebView2 1.0.3719.77: [Microsoft.Web.WebView2-1.0.3719.77-LICENSE.txt](THIRD-PARTY-LICENSES/Microsoft.Web.WebView2-1.0.3719.77-LICENSE.txt)
- Microsoft.Web.WebView2 1.0.3719.77: [Microsoft.Web.WebView2-1.0.3719.77-NOTICE.txt](THIRD-PARTY-LICENSES/Microsoft.Web.WebView2-1.0.3719.77-NOTICE.txt)
- Microsoft.Windows.AI.MachineLearning 2.1.74: [Microsoft.Windows.AI.MachineLearning-2.1.74-ThirdPartyNotices.txt](THIRD-PARTY-LICENSES/Microsoft.Windows.AI.MachineLearning-2.1.74-ThirdPartyNotices.txt)
- Microsoft.Windows.AI.MachineLearning 2.1.74: [Microsoft.Windows.AI.MachineLearning-2.1.74-license.txt](THIRD-PARTY-LICENSES/Microsoft.Windows.AI.MachineLearning-2.1.74-license.txt)
- Microsoft.WindowsAppSDK 2.5.1: [Microsoft.WindowsAppSDK.AI-2.5.5-license.txt](THIRD-PARTY-LICENSES/Microsoft.WindowsAppSDK.AI-2.5.5-license.txt)
- Microsoft.WindowsAppSDK 2.5.1: [Microsoft.WindowsAppSDK.Base-2.0.4-NOTICE.txt](THIRD-PARTY-LICENSES/Microsoft.WindowsAppSDK.Base-2.0.4-NOTICE.txt)
- Microsoft.WindowsAppSDK.AI 2.5.5: [Microsoft.WindowsAppSDK.AI-2.5.5-license.txt](THIRD-PARTY-LICENSES/Microsoft.WindowsAppSDK.AI-2.5.5-license.txt)
- Microsoft.WindowsAppSDK.Base 2.0.4: [Microsoft.WindowsAppSDK.AI-2.5.5-license.txt](THIRD-PARTY-LICENSES/Microsoft.WindowsAppSDK.AI-2.5.5-license.txt)
- Microsoft.WindowsAppSDK.Base 2.0.4: [Microsoft.WindowsAppSDK.Base-2.0.4-NOTICE.txt](THIRD-PARTY-LICENSES/Microsoft.WindowsAppSDK.Base-2.0.4-NOTICE.txt)
- Microsoft.WindowsAppSDK.DWrite 2.1.0: [Microsoft.WindowsAppSDK.AI-2.5.5-license.txt](THIRD-PARTY-LICENSES/Microsoft.WindowsAppSDK.AI-2.5.5-license.txt)
- Microsoft.WindowsAppSDK.Foundation 2.3.12: [Microsoft.WindowsAppSDK.AI-2.5.5-license.txt](THIRD-PARTY-LICENSES/Microsoft.WindowsAppSDK.AI-2.5.5-license.txt)
- Microsoft.WindowsAppSDK.InteractiveExperiences 2.1.9: [Microsoft.WindowsAppSDK.AI-2.5.5-license.txt](THIRD-PARTY-LICENSES/Microsoft.WindowsAppSDK.AI-2.5.5-license.txt)
- Microsoft.WindowsAppSDK.ML 2.1.94: [Microsoft.WindowsAppSDK.ML-2.1.94-license.txt](THIRD-PARTY-LICENSES/Microsoft.WindowsAppSDK.ML-2.1.94-license.txt)
- Microsoft.WindowsAppSDK.Runtime 2.5.1: [Microsoft.WindowsAppSDK.AI-2.5.5-license.txt](THIRD-PARTY-LICENSES/Microsoft.WindowsAppSDK.AI-2.5.5-license.txt)
- Microsoft.WindowsAppSDK.Runtime 2.5.1: [Microsoft.WindowsAppSDK.Base-2.0.4-NOTICE.txt](THIRD-PARTY-LICENSES/Microsoft.WindowsAppSDK.Base-2.0.4-NOTICE.txt)
- Microsoft.WindowsAppSDK.Search 2.5.5: [Microsoft.WindowsAppSDK.AI-2.5.5-license.txt](THIRD-PARTY-LICENSES/Microsoft.WindowsAppSDK.AI-2.5.5-license.txt)
- Microsoft.WindowsAppSDK.Widgets 2.0.5: [Microsoft.WindowsAppSDK.AI-2.5.5-license.txt](THIRD-PARTY-LICENSES/Microsoft.WindowsAppSDK.AI-2.5.5-license.txt)
- Microsoft.WindowsAppSDK.WinUI 2.3.9: [Microsoft.WindowsAppSDK.AI-2.5.5-license.txt](THIRD-PARTY-LICENSES/Microsoft.WindowsAppSDK.AI-2.5.5-license.txt)
- Microsoft.WindowsAppSDK.WinUI 2.3.9: [Microsoft.WindowsAppSDK.WinUI-2.3.9-NOTICE.txt](THIRD-PARTY-LICENSES/Microsoft.WindowsAppSDK.WinUI-2.3.9-NOTICE.txt)
- Microsoft.WindowsAppSDK.WinUI 2.3.9: [Microsoft.WindowsAppSDK.WinUI-2.3.9-tools-NOTICE.txt](THIRD-PARTY-LICENSES/Microsoft.WindowsAppSDK.WinUI-2.3.9-tools-NOTICE.txt)
- System.Numerics.Tensors 9.0.0: [System.Numerics.Tensors-9.0.0-LICENSE.TXT](THIRD-PARTY-LICENSES/System.Numerics.Tensors-9.0.0-LICENSE.TXT)
- System.Numerics.Tensors 9.0.0: [System.Numerics.Tensors-9.0.0-THIRD-PARTY-NOTICES.TXT](THIRD-PARTY-LICENSES/System.Numerics.Tensors-9.0.0-THIRD-PARTY-NOTICES.TXT)
## Reference document stylesheet and containers
The embedded `Themes/Document.css` and document container/navigation adaptations
are based on Darin Kelkhoff's MIT-licensed markdownViewr at commit
`6d08bc9d27d7d7b2c2fa95bb282da330b8cf9021`. The original document rules, variable
aliases and `#content` / `#content-inner` structure are retained with Windows
sanitization, scrolling and print integration. Source files retain Darin's
copyright and MIT identifiers; the full original MIT notice above accompanies
the application. Original project: <https://github.com/darinkelkhoff/markdownViewr>.
Author website: <https://kelkhoff.com/>.
+2 -2
View File
@@ -29,11 +29,11 @@ Describe 'Offline setup preview location' `
Describe 'Required checks match standalone workflow jobs' `
{
It 'requires the actual check names from both CI and script workflows' `
It 'requires the actual check names from CI, script and fuzz workflows' `
{
$plan = Get-GitHubRepositoryPlan -Repository 'Example/Notes'
$workflowRoot = Join-Path (Split-Path -Parent $PSScriptRoot) '.github/workflows'
foreach($workflow in @('ci.yml', 'scripts.yml'))
foreach($workflow in @('ci.yml', 'scripts.yml', 'fuzz.yml'))
{
$names = @(Select-String -LiteralPath (Join-Path $workflowRoot $workflow) -Pattern '^ name: ')
$names.Count | Should -BeGreaterThan 0
+1 -1
View File
@@ -22,7 +22,7 @@ function Get-GitHubRepositoryPlan
$contexts = @('pr / title', 'pr / dco', 'pr / dependency-review', 'security / secrets',
'security / sca', 'security / workflows', 'codeql / analyze (csharp)',
'codeql / analyze (actions)', 'codeql / analyze (javascript-typescript)',
'ci / build-test', 'ci / format', 'ci / ui-tests', 'installed-updates', 'powershell', 'javascript')
'ci / build-test', 'ci / format', 'ci / ui-tests', 'installed-updates', 'reader-regressions', 'fuzzing', 'powershell', 'javascript')
if(Test-Path -LiteralPath (Join-Path (Split-Path -Parent $PSScriptRoot) 'templates/KofTwentyTwo.Templates.WindowsApp.csproj'))
{
$contexts += 'template'
+123 -2
View File
@@ -13,8 +13,26 @@ Describe 'Installed update smoke boundaries' `
$script:SmokeArtifacts = Join-Path (Split-Path -Parent $PSScriptRoot) 'artifacts'
$script:SmokeAppId = [regex]::Match((Get-Content -LiteralPath (Join-Path (Split-Path -Parent $PSScriptRoot) 'src/MarkdownViewr.Windows.Core/AppIdentity.cs') -Raw), '(?m)^\s*Id\s*=\s*"([a-z0-9][a-z0-9-]*)"').Groups[1].Value
$ast = [Management.Automation.Language.Parser]::ParseFile($script:SmokeHarness, [ref]$null, [ref]$null)
$versionCheck = $ast.Find({ param($node) $node -is [Management.Automation.Language.FunctionDefinitionAst] -and $node.Name -eq 'Assert-SmokeCliVersion' }, $true)
. ([scriptblock]::Create($versionCheck.Extent.Text))
foreach($name in @('Assert-SmokeCliVersion', 'Get-SmokeDefaultChoiceSnapshot', 'Get-SmokeFileRegistration', 'Assert-SmokeFileRegistrationRemoved', 'Assert-SmokeInstallationOwner'))
{
$function = $ast.Find({ param($node) $node -is [Management.Automation.Language.FunctionDefinitionAst] -and $node.Name -eq $name }, $true)
. ([scriptblock]::Create($function.Extent.Text))
}
}
It 'requires an actual matching manifest inside the resolved artifact boundary before uninstall' `
{
$root = Join-Path $TestDrive 'artifacts'
$install = Join-Path $root 'owned installation'
New-Item -ItemType Directory -Path (Join-Path $install 'current') -Force | Out-Null
$manifest = Join-Path $install 'current/sq.version'
'<package><metadata><id>owned-stable</id><version>0.4.0</version></metadata></package>' | Set-Content -LiteralPath $manifest
{ Assert-SmokeInstallationOwner -Install $install -PackageIdentity 'owned-stable' -ArtifactRoot $root } | Should -Not -Throw
{ Assert-SmokeInstallationOwner -Install $install -PackageIdentity 'owned-dev' -ArtifactRoot $root } | Should -Throw '*unexpected application*'
{ Assert-SmokeInstallationOwner -Install (Join-Path $root '../outside') -PackageIdentity 'owned-stable' -ArtifactRoot $root } | Should -Throw '*outside the smoke artifact boundary*'
{ Assert-SmokeInstallationOwner -Install (Join-Path $TestDrive 'artifacts-other') -PackageIdentity 'owned-stable' -ArtifactRoot $root } | Should -Throw '*outside the smoke artifact boundary*'
{ Assert-SmokeInstallationOwner -Install (Join-Path $root 'missing') -PackageIdentity 'owned-stable' -ArtifactRoot $root } | Should -Throw
$manifest | Should -Exist
}
It 'accepts CLI version without Git metadata outside a checkout' `
@@ -59,3 +77,106 @@ Describe 'Installed update smoke boundaries' `
finally { Remove-Item -LiteralPath $output }
}
}
Describe 'Actual registry readbacks for installed file types' `
{
BeforeAll `
{
$ast = [Management.Automation.Language.Parser]::ParseFile((Join-Path $PSScriptRoot 'Test-InstalledUpdates.ps1'), [ref]$null, [ref]$null)
foreach($name in @('Get-SmokeDefaultChoiceSnapshot', 'Get-SmokeFileRegistration', 'Assert-SmokeFileRegistrationRemoved'))
{
$function = $ast.Find({ param($node) $node -is [Management.Automation.Language.FunctionDefinitionAst] -and $node.Name -eq $name }, $true)
. ([scriptblock]::Create($function.Extent.Text))
}
}
BeforeEach `
{
$script:FixtureRelative = 'Software\KofTwentyTwo\MarkdownViewr.Windows.SmokeRegistrationTests\' + [guid]::NewGuid().ToString('N')
$script:FixtureRoot = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey($script:FixtureRelative)
$script:Exe = Join-Path $TestDrive 'installed app/current/MarkdownViewr.Windows.exe'
$script:Identity = 'KofTwentyTwo.MarkdownViewr.stable.fixture'
$script:Capabilities = "Software\KofTwentyTwo\MarkdownViewr\FileAssociations\$script:Identity\Capabilities"
$key = $script:FixtureRoot.CreateSubKey('Software\RegisteredApplications')
$key.SetValue($script:Identity, $script:Capabilities)
$key.Dispose()
$key = $script:FixtureRoot.CreateSubKey("Software\Classes\$script:Identity\shell\open\command")
$key.SetValue('', '"' + $script:Exe + '" "%1"')
$key.Dispose()
$key = $script:FixtureRoot.CreateSubKey("$script:Capabilities\FileAssociations")
foreach($extension in @('.md', '.markdown', '.mdown', '.mkd', '.txt')) { $key.SetValue($extension, $script:Identity) }
$key.Dispose()
foreach($extension in @('.md', '.markdown', '.mdown', '.mkd', '.txt'))
{
$key = $script:FixtureRoot.CreateSubKey("Software\Classes\$extension")
$key.SetValue('', 'Other.Reader')
$key.Dispose()
$key = $script:FixtureRoot.CreateSubKey("Software\Classes\$extension\OpenWithProgids")
$key.SetValue($script:Identity, '')
$key.SetValue('Other.Reader', '')
$key.Dispose()
$key = $script:FixtureRoot.CreateSubKey("Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\$extension\UserChoice")
$key.SetValue('ProgId', 'Other.Reader')
$key.SetValue('Hash', 'existing-choice-fixture')
$key.Dispose()
}
}
AfterEach `
{
$script:FixtureRoot.Dispose()
[Microsoft.Win32.Registry]::CurrentUser.DeleteSubKeyTree($script:FixtureRelative)
}
It 'recognizes only the exact installed executable and channel without changing defaults' `
{
$before = Get-SmokeDefaultChoiceSnapshot -RegistryRoot $script:FixtureRoot | ConvertTo-Json -Depth 3 -Compress
$registration = Get-SmokeFileRegistration -Exe $script:Exe -Channel stable -RegistryRoot $script:FixtureRoot
$registration.ProgId | Should -BeExactly $script:Identity
$registration.ExtensionsVerified | Should -Be 5
{ Get-SmokeFileRegistration -Exe $script:Exe -Channel dev -RegistryRoot $script:FixtureRoot } | Should -Throw '*exactly one*'
{ Get-SmokeFileRegistration -Exe (Join-Path $TestDrive 'other.exe') -Channel stable -RegistryRoot $script:FixtureRoot } | Should -Throw '*exactly one*'
(Get-SmokeDefaultChoiceSnapshot -RegistryRoot $script:FixtureRoot | ConvertTo-Json -Depth 3 -Compress) | Should -BeExactly $before
}
It 'rejects a missing Open With registration for <MissingExtension>' -ForEach @(
@{ MissingExtension = '.md' }, @{ MissingExtension = '.markdown' }, @{ MissingExtension = '.mdown' }, @{ MissingExtension = '.mkd' }, @{ MissingExtension = '.txt' }
) `
{
$key = $script:FixtureRoot.OpenSubKey("Software\Classes\$MissingExtension\OpenWithProgids", $true)
$key.DeleteValue($script:Identity)
$key.Dispose()
{ Get-SmokeFileRegistration -Exe $script:Exe -Channel stable -RegistryRoot $script:FixtureRoot } | Should -Throw '*Open With extension*'
}
It 'rejects an advertised extension mapped to a different application' `
{
$key = $script:FixtureRoot.OpenSubKey("$script:Capabilities\FileAssociations", $true)
$key.SetValue('.txt', 'Other.Reader')
$key.Dispose()
{ Get-SmokeFileRegistration -Exe $script:Exe -Channel stable -RegistryRoot $script:FixtureRoot } | Should -Throw '*Default Apps extension*'
}
It 'requires complete owned cleanup while leaving unrelated Open With and defaults intact' `
{
$before = Get-SmokeDefaultChoiceSnapshot -RegistryRoot $script:FixtureRoot | ConvertTo-Json -Depth 3 -Compress
$registration = Get-SmokeFileRegistration -Exe $script:Exe -Channel stable -RegistryRoot $script:FixtureRoot
{ Assert-SmokeFileRegistrationRemoved -Registration $registration -RegistryRoot $script:FixtureRoot } | Should -Throw '*registration remains*'
$script:FixtureRoot.DeleteSubKeyTree("Software\Classes\$script:Identity")
$script:FixtureRoot.DeleteSubKeyTree($script:Capabilities)
$key = $script:FixtureRoot.OpenSubKey('Software\RegisteredApplications', $true)
$key.DeleteValue($script:Identity)
$key.Dispose()
{ Assert-SmokeFileRegistrationRemoved -Registration $registration -RegistryRoot $script:FixtureRoot } | Should -Throw '*Open With entry remains*'
foreach($extension in @('.md', '.markdown', '.mdown', '.mkd', '.txt'))
{
$key = $script:FixtureRoot.OpenSubKey("Software\Classes\$extension\OpenWithProgids", $true)
$key.DeleteValue($script:Identity)
$key.GetValue('Other.Reader') | Should -BeExactly ''
$key.Dispose()
}
{ Assert-SmokeFileRegistrationRemoved -Registration $registration -RegistryRoot $script:FixtureRoot } | Should -Not -Throw
(Get-SmokeDefaultChoiceSnapshot -RegistryRoot $script:FixtureRoot | ConvertTo-Json -Depth 3 -Compress) | Should -BeExactly $before
}
}
+286 -12
View File
@@ -42,8 +42,12 @@ Set-Content -LiteralPath $idFile -Value $packageId
Set-Content -LiteralPath (Join-Path $outputRoot 'build-directory.txt') -Value $buildRoot
$fixtureRoot = Join-Path $outputRoot 'fixture'
$dataVariable = $AppId.ToUpperInvariant().Replace('-', '_') + '_DATA_DIR'
$devDataVariable = ($AppId + '-dev').ToUpperInvariant().Replace('-', '_') + '_DATA_DIR'
$oldDataDirectory = [Environment]::GetEnvironmentVariable($dataVariable)
$oldDevDataDirectory = [Environment]::GetEnvironmentVariable($devDataVariable)
$oldUiChannel = $env:K22_UITEST_CHANNEL
$oldUiExe = $env:K22_UITEST_EXE
$oldUiInstalled = $env:K22_UITEST_INSTALLED
$versions = @(
@{ channel = 'stable'; version = '0.1.0'; target = '0.4.0' },
@{ channel = 'stable'; version = '0.4.0'; target = $null },
@@ -52,6 +56,92 @@ $versions = @(
)
$script:smokeProcessNumber = 0
<#
.SYNOPSIS
Reads Windows default choices without changing them or retaining their private hashes in evidence.
#>
function Get-SmokeDefaultChoiceSnapshot
{
param([Microsoft.Win32.RegistryKey]$RegistryRoot = [Microsoft.Win32.Registry]::CurrentUser)
foreach($extension in @('.md', '.markdown', '.mdown', '.mkd', '.txt'))
{
$choice = "$($RegistryRoot.Name)\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\$extension\UserChoice"
[ordered]@{
Extension = $extension
ExtensionDefault = [Microsoft.Win32.Registry]::GetValue("$($RegistryRoot.Name)\Software\Classes\$extension", '', $null)
ProgId = [Microsoft.Win32.Registry]::GetValue($choice, 'ProgId', $null)
Hash = [Microsoft.Win32.Registry]::GetValue($choice, 'Hash', $null)
}
}
}
<#
.SYNOPSIS
Requires actual installer-owned Open With and Default Apps registration for all five advertised extensions.
#>
function Get-SmokeFileRegistration
{
param(
[Parameter(Mandatory)][string]$Exe,
[Parameter(Mandatory)][ValidateSet('stable', 'dev')][string]$Channel,
[Microsoft.Win32.RegistryKey]$RegistryRoot = [Microsoft.Win32.Registry]::CurrentUser
)
$Exe = [IO.Path]::GetFullPath($Exe)
$applications = $RegistryRoot.OpenSubKey('Software\RegisteredApplications')
$found = @()
try
{
if($null -ne $applications)
{
foreach($identity in $applications.GetValueNames())
{
if(-not $identity.StartsWith("KofTwentyTwo.MarkdownViewr.$Channel.", [StringComparison]::Ordinal)) { continue }
$command = [Microsoft.Win32.Registry]::GetValue("$($RegistryRoot.Name)\Software\Classes\$identity\shell\open\command", '', $null)
if($command -cne ('"' + $Exe + '" "%1"')) { continue }
$capabilities = $applications.GetValue($identity)
if($capabilities -cne "Software\KofTwentyTwo\MarkdownViewr\FileAssociations\$identity\Capabilities") { throw 'The installed Default Apps capabilities are incorrect.' }
foreach($extension in @('.md', '.markdown', '.mdown', '.mkd', '.txt'))
{
if([Microsoft.Win32.Registry]::GetValue("$($RegistryRoot.Name)\$capabilities\FileAssociations", $extension, $null) -cne $identity) { throw 'An installed Default Apps extension is missing or incorrect.' }
if($null -eq [Microsoft.Win32.Registry]::GetValue("$($RegistryRoot.Name)\Software\Classes\$extension\OpenWithProgids", $identity, $null)) { throw 'An installed Open With extension is missing.' }
}
$found += [pscustomobject]@{ ProgId = $identity; Capabilities = $capabilities; ExtensionsVerified = 5 }
}
}
}
finally { if($null -ne $applications) { $applications.Dispose() } }
if($found.Count -ne 1) { throw 'Expected exactly one file registration for this installed executable and channel.' }
return $found[0]
}
<#
.SYNOPSIS
Requires uninstall to remove only the recorded installation's file-type registrations.
#>
function Assert-SmokeFileRegistrationRemoved
{
param(
[Parameter(Mandatory)][object]$Registration,
[Microsoft.Win32.RegistryKey]$RegistryRoot = [Microsoft.Win32.Registry]::CurrentUser
)
foreach($path in @("Software\Classes\$($Registration.ProgId)", $Registration.Capabilities))
{
$key = $RegistryRoot.OpenSubKey($path)
if($null -ne $key) { $key.Dispose(); throw 'An uninstalled file-type registration remains.' }
}
if($null -ne [Microsoft.Win32.Registry]::GetValue("$($RegistryRoot.Name)\Software\RegisteredApplications", $Registration.ProgId, $null)) { throw 'An uninstalled Default Apps entry remains.' }
foreach($extension in @('.md', '.markdown', '.mdown', '.mkd', '.txt'))
{
if($null -ne [Microsoft.Win32.Registry]::GetValue("$($RegistryRoot.Name)\Software\Classes\$extension\OpenWithProgids", $Registration.ProgId, $null)) { throw 'An uninstalled Open With entry remains.' }
}
}
<#
.SYNOPSIS
Runs one process with literal arguments, bounded duration and checked exit status.
@@ -77,15 +167,17 @@ function Invoke-SmokeProcess
$stderr = $process.StandardError.ReadToEndAsync()
try
{
if(-not $process.WaitForExit($TimeoutSeconds * 1000))
$timedOut = -not $process.WaitForExit($TimeoutSeconds * 1000)
if($timedOut)
{
$process.Kill($true)
throw "Process timed out: $File"
if(-not $process.WaitForExit(10000)) { throw "Timed-out process did not terminate: $File" }
}
$script:smokeProcessNumber++
$log = Join-Path $outputRoot "process-$script:smokeProcessNumber.log"
$text = $stdout.GetAwaiter().GetResult() + $stderr.GetAwaiter().GetResult()
Set-Content -LiteralPath $log -Value $text
if($timedOut) { throw "Process timed out: $File. Log: $log" }
if($process.ExitCode -ne 0) { throw "Process failed ($($process.ExitCode)): $File. Log: $log`n$text" }
}
finally { $process.Dispose() }
@@ -142,12 +234,170 @@ function Stop-SmokeApp
}
}
<#
.SYNOPSIS
Waits for a real window from the exact owned executable, optionally with an exact document title.
#>
function Get-SmokeReaderWindow
{
param([string]$Exe, [string]$Title)
$deadline = [DateTime]::UtcNow.AddSeconds(30)
do
{
foreach($process in @(Get-Process -Name $name -ErrorAction SilentlyContinue))
{
try
{
if($process.Path -eq $Exe -and $process.MainWindowHandle -ne 0 -and ([string]::IsNullOrEmpty($Title) -or $process.MainWindowTitle -ceq $Title))
{
return [pscustomobject]@{ ProcessId = $process.Id; Title = $process.MainWindowTitle }
}
}
finally { $process.Dispose() }
}
Start-Sleep -Milliseconds 250
} while([DateTime]::UtcNow -lt $deadline)
throw 'The exact installed reader did not show its expected native window.'
}
<#
.SYNOPSIS
Requires manifest identity and the resolved artifact boundary before any owned uninstall.
#>
function Assert-SmokeInstallationOwner
{
param([string]$Install, [string]$PackageIdentity, [string]$ArtifactRoot)
$resolved = [IO.Path]::GetFullPath($Install)
$boundary = [IO.Path]::GetFullPath($ArtifactRoot).TrimEnd([IO.Path]::DirectorySeparatorChar) + [IO.Path]::DirectorySeparatorChar
if(-not $resolved.StartsWith($boundary, [StringComparison]::OrdinalIgnoreCase)) { throw 'Refusing to uninstall outside the smoke artifact boundary.' }
$manifest = [xml](Get-Content -LiteralPath (Join-Path $resolved 'current/sq.version') -Raw -ErrorAction Stop)
if($manifest.package.metadata.id -cne $PackageIdentity) { throw 'Refusing to uninstall an unexpected application.' }
}
<#
.SYNOPSIS
Uninstalls only a verified owned fixture and requires its registration cleanup without changing user defaults.
#>
function Remove-SmokeInstallation
{
[CmdletBinding(SupportsShouldProcess)]
param([object]$State, [string]$DefaultChoices)
if(-not (Test-Path -LiteralPath $State.Install)) { return }
Assert-SmokeInstallationOwner -Install $State.Install -PackageIdentity $State.PackageId -ArtifactRoot $allowedRoot
if(-not $PSCmdlet.ShouldProcess($State.Install, 'Uninstall owned simultaneous-channel fixture')) { return }
Stop-SmokeApp -Exe $State.Exe -Confirm:$false
Invoke-SmokeProcess -File (Join-Path $State.Install 'Update.exe') -Arguments @('--silent', '--rootDir', $State.Install, 'uninstall')
$deadline = [DateTime]::UtcNow.AddSeconds(10)
while((Test-Path -LiteralPath $State.Install) -and [DateTime]::UtcNow -lt $deadline) { Start-Sleep -Milliseconds 200 }
if(Test-Path -LiteralPath $State.Install) { throw 'The owned simultaneous-channel installation remains after uninstall.' }
if(Test-Path -LiteralPath "HKCU:/Software/Microsoft/Windows/CurrentVersion/Uninstall/$($State.PackageId)") { throw 'The owned uninstall registry entry remains.' }
if($null -ne $State.Registration) { Assert-SmokeFileRegistrationRemoved -Registration $State.Registration }
if($DefaultChoices -cne (Get-SmokeDefaultChoiceSnapshot | ConvertTo-Json -Depth 3 -Compress)) { throw 'Simultaneous-channel uninstall changed Windows defaults.' }
}
<#
.SYNOPSIS
Keeps both real editions installed and open while updating and uninstalling each without disturbing its sibling.
#>
function Test-SmokeCoexistingChannel
{
$states = [Collections.Generic.List[object]]::new()
$defaultChoices = Get-SmokeDefaultChoiceSnapshot | ConvertTo-Json -Depth 3 -Compress
try
{
foreach($initial in @($versions[0], $versions[2]))
{
$channel = $initial.channel
$install = Join-Path $outputRoot "coexisting-$channel"
$data = Join-Path $outputRoot "coexisting-data-$channel"
if((Test-Path -LiteralPath $install) -or (Test-Path -LiteralPath $data)) { throw 'Simultaneous-channel fixtures must be fresh.' }
New-Item -ItemType Directory -Path $data | Out-Null
[ordered]@{ ShowSplashScreen = $false; CheckForUpdatesOnStartup = $false; DocumentTheme = $(if($channel -eq 'dev') { 'GitHub Light' } else { 'Catppuccin Mocha' }) } | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $data 'settings.json')
[Environment]::SetEnvironmentVariable($(if($channel -eq 'dev') { $devDataVariable } else { $dataVariable }), $data)
$state = [pscustomobject]@{ Channel = $channel; Install = $install; Data = $data; PackageId = "$packageId-$channel"; Exe = (Join-Path $install "current/$mainExe"); Target = $initial.target; Registration = $null; Window = $null }
$states.Add($state)
$setup = Join-Path $fixtureRoot "$($initial.version)/$($state.PackageId)-$channel-Setup.exe"
Invoke-SmokeProcess -File $setup -Arguments @('--silent', '--installto', $install, '--log', (Join-Path $outputRoot "coexisting-install-$channel.log"))
$state.Registration = Get-SmokeFileRegistration -Exe $state.Exe -Channel $channel
Assert-SmokeInstallationOwner -Install $install -PackageIdentity $state.PackageId -ArtifactRoot $allowedRoot
}
if($states[0].Registration.ProgId -ceq $states[1].Registration.ProgId) { throw 'Stable and dev share a file-type owner.' }
foreach($state in $states)
{
Stop-SmokeApp -Exe $state.Exe -Confirm:$false
$document = Join-Path $state.Data "$($state.Channel).md"
'# Simultaneous channel fixture' | Set-Content -LiteralPath $document
$start = [Diagnostics.ProcessStartInfo]::new($state.Exe)
$start.UseShellExecute = $false
$start.ArgumentList.Add($document)
$process = [Diagnostics.Process]::Start($start)
$process.Dispose()
$displayName = if($state.Channel -eq 'dev') { 'MarkdownViewr (dev)' } else { 'MarkdownViewr' }
$state.Window = Get-SmokeReaderWindow -Exe $state.Exe -Title ("$($state.Channel).md " + [char]0x2014 + " $displayName")
$logPrefix = if($state.Channel -eq 'dev') { "$AppId-dev" } else { $AppId }
$logs = @(Get-ChildItem -LiteralPath (Join-Path $state.Data 'logs') -Filter "$logPrefix-*.log")
if($logs.Count -eq 0 -or -not ((Get-Content -LiteralPath $logs.FullName -Raw) -match 'Opened a Markdown document')) { throw 'The actual reader did not use its own edition profile/log prefix.' }
}
if($states[0].Window.ProcessId -eq $states[1].Window.ProcessId) { throw 'Stable and dev incorrectly share one process.' }
foreach($state in $states)
{
$sibling = @($states | Where-Object Channel -NE $state.Channel)[0]
$siblingWindow = Get-SmokeReaderWindow -Exe $sibling.Exe
$siblingHash = (Get-FileHash -LiteralPath (Join-Path $sibling.Install "current/$name.dll") -Algorithm SHA256).Hash
$siblingSettings = (Get-FileHash -LiteralPath (Join-Path $sibling.Data 'settings.json') -Algorithm SHA256).Hash
Stop-SmokeApp -Exe $state.Exe -Confirm:$false
$before = Get-SmokeResult -Exe (Join-Path $state.Install "current/$probeExe") -ResultPath (Join-Path $outputRoot "coexisting-$($state.Channel)-before.json") -Mode 'apply'
if($before.Channel -cne $state.Channel -or $before.UpdateVersion -cne $state.Target) { throw 'The simultaneous edition received an incorrect update offer.' }
$deadline = [DateTime]::UtcNow.AddSeconds(90)
do
{
Start-Sleep -Milliseconds 300
$manifestPath = Join-Path $state.Install 'current/sq.version'
$manifest = if(Test-Path -LiteralPath $manifestPath) { [xml](Get-Content -LiteralPath $manifestPath -Raw) } else { $null }
} while(($null -eq $manifest -or $manifest.package.metadata.version -cne $state.Target) -and [DateTime]::UtcNow -lt $deadline)
if($null -eq $manifest -or $manifest.package.metadata.version -cne $state.Target) { throw 'The simultaneous edition did not install its target update.' }
Assert-SmokeInstallationOwner -Install $state.Install -PackageIdentity $state.PackageId -ArtifactRoot $allowedRoot
$state.Window = Get-SmokeReaderWindow -Exe $state.Exe
$targetHash = (Get-FileHash -LiteralPath (Join-Path $buildRoot "publish/$($state.Target)/$name.dll") -Algorithm SHA256).Hash
if((Get-FileHash -LiteralPath (Join-Path $state.Install "current/$name.dll") -Algorithm SHA256).Hash -cne $targetHash) { throw 'The simultaneous target bytes do not match the published assembly.' }
$registration = Get-SmokeFileRegistration -Exe $state.Exe -Channel $state.Channel
if($registration.ProgId -cne $state.Registration.ProgId) { throw 'The simultaneous update changed its file-type identity.' }
$after = Get-SmokeResult -Exe (Join-Path $state.Install "current/$probeExe") -ResultPath (Join-Path $outputRoot "coexisting-$($state.Channel)-after.json")
if($after.Channel -cne $state.Channel -or $after.Version -cne $state.Target -or $after.AppKitVersion -cne $state.Target -or $null -ne $after.UpdateVersion) { throw 'The simultaneous edition has an incorrect post-update identity.' }
if((Get-SmokeReaderWindow -Exe $sibling.Exe).ProcessId -ne $siblingWindow.ProcessId -or (Get-FileHash -LiteralPath (Join-Path $sibling.Install "current/$name.dll") -Algorithm SHA256).Hash -cne $siblingHash -or (Get-FileHash -LiteralPath (Join-Path $sibling.Data 'settings.json') -Algorithm SHA256).Hash -cne $siblingSettings) { throw 'Updating an edition disturbed its live sibling or preferences.' }
if((Get-SmokeFileRegistration -Exe $sibling.Exe -Channel $sibling.Channel).ProgId -cne $sibling.Registration.ProgId) { throw 'Updating an edition changed its sibling registration.' }
if($defaultChoices -cne (Get-SmokeDefaultChoiceSnapshot | ConvertTo-Json -Depth 3 -Compress)) { throw 'Simultaneous installation/update changed Windows defaults.' }
}
$devWindow = Get-SmokeReaderWindow -Exe $states[1].Exe
Remove-SmokeInstallation -State $states[0] -DefaultChoices $defaultChoices -Confirm:$false
if((Get-SmokeReaderWindow -Exe $states[1].Exe).ProcessId -ne $devWindow.ProcessId -or (Get-SmokeFileRegistration -Exe $states[1].Exe -Channel 'dev').ProgId -cne $states[1].Registration.ProgId) { throw 'Uninstalling stable disturbed the installed running dev edition.' }
[ordered]@{ SimultaneousWindows = $true; DistinctProfiles = $true; IsolatedUpdates = $true; StableUninstallPreservedDev = $true; ExistingDefaultsPreserved = $true; Editions = @($states | Select-Object Channel, PackageId, Target, Registration, Window) } | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $outputRoot 'coexisting-channels.json')
}
finally
{
$cleanupFailures = [Collections.Generic.List[string]]::new()
foreach($state in $states)
{
try { Remove-SmokeInstallation -State $state -DefaultChoices $defaultChoices -Confirm:$false }
catch { $cleanupFailures.Add($_.Exception.Message) }
}
if($cleanupFailures.Count -gt 0) { throw ($cleanupFailures -join '; ') }
}
}
$records = [Collections.Generic.List[object]]::new()
try
{
foreach($release in $versions)
{
$version = $release.version
$releasePackageId = $packageId + '-' + $release.channel
Write-Output "Packaging $version ($($release.channel))"
$publish = Join-Path $buildRoot "publish/$version"
$probe = Join-Path $buildRoot "probe/$version"
@@ -165,8 +415,8 @@ try
$versionOutput = Get-Content -LiteralPath (Join-Path $outputRoot "process-$script:smokeProcessNumber.log") -Raw
Assert-SmokeCliVersion -Output $versionOutput -Version $version
}
Invoke-SmokeProcess -File $vpk -Arguments @('pack', '--runtime', 'win-x64', '--packId', $packageId, '--packVersion', $version, '--packDir', $publish, '--mainExe', $mainExe, '--channel', $release.channel, '--outputDir', (Join-Path $fixtureRoot $version), '--shortcuts', 'None', '--delta', 'None', '--skip-updates', '--yes') -TimeoutSeconds 300
& "$PSScriptRoot/Assert-PackageBytes.ps1" -PublishDirectory $publish -PortableArchive (Join-Path $fixtureRoot "$version/$packageId-$($release.channel)-Portable.zip") -FullPackage (Join-Path $fixtureRoot "$version/$packageId-$version-$($release.channel)-full.nupkg")
Invoke-SmokeProcess -File $vpk -Arguments @('pack', '--runtime', 'win-x64', '--packId', $releasePackageId, '--packVersion', $version, '--packDir', $publish, '--mainExe', $mainExe, '--channel', $release.channel, '--outputDir', (Join-Path $fixtureRoot $version), '--shortcuts', 'None', '--delta', 'None', '--skip-updates', '--yes') -TimeoutSeconds 300
& "$PSScriptRoot/Assert-PackageBytes.ps1" -PublishDirectory $publish -PortableArchive (Join-Path $fixtureRoot "$version/$releasePackageId-$($release.channel)-Portable.zip") -FullPackage (Join-Path $fixtureRoot "$version/$releasePackageId-$version-$($release.channel)-full.nupkg")
}
$releases = foreach($release in $versions)
{
@@ -182,12 +432,15 @@ try
$loose = Get-SmokeResult -Exe (Join-Path $buildRoot "publish/0.1.0/$probeExe") -ResultPath (Join-Path $outputRoot 'loose.json')
if($loose.Installed -or $loose.AppKitSupported -or $null -ne $loose.Version) { throw 'Loose publish output unexpectedly supports updates.' }
$records.Add(@{ kind = 'loose'; result = $loose })
$env:K22_UITEST_CHANNEL = 'stable'
$env:K22_UITEST_INSTALLED = '0'
$env:K22_UITEST_EXE = Join-Path $buildRoot "publish/0.1.0/$mainExe"
Invoke-SmokeProcess -File 'dotnet' -Arguments @('test', "$repositoryRoot/tests/$name.UiTests", '--no-build', '--logger', 'trx;LogFileName=published-ui.trx', '--results-directory', $outputRoot) -TimeoutSeconds 180
Invoke-SmokeProcess -File 'dotnet' -Arguments @('test', "$repositoryRoot/tests/$name.UiTests", '--no-build', '--logger', 'console;verbosity=normal', '--logger', 'trx;LogFileName=published-ui.trx', '--results-directory', $outputRoot) -TimeoutSeconds 600
foreach($initial in @($versions[0], $versions[2]))
{
$initialPackageId = $packageId + '-' + $initial.channel
$portableDirectory = Join-Path $outputRoot "portable-$($initial.channel)"
[IO.Compression.ZipFile]::ExtractToDirectory((Join-Path $fixtureRoot "$($initial.version)/$packageId-$($initial.channel)-Portable.zip"), $portableDirectory)
[IO.Compression.ZipFile]::ExtractToDirectory((Join-Path $fixtureRoot "$($initial.version)/$initialPackageId-$($initial.channel)-Portable.zip"), $portableDirectory)
$portable = Get-SmokeResult -Exe (Join-Path $portableDirectory "current/$probeExe") -ResultPath (Join-Path $outputRoot "$($initial.channel)-portable.json")
if(-not $portable.Installed -or -not $portable.IsPortable -or -not $portable.AppKitSupported -or $portable.Channel -ne $initial.channel -or $portable.UpdateVersion -ne $initial.target) { throw 'Portable bundle identity/update behavior is incorrect.' }
$records.Add(@{ kind = 'portable'; result = $portable })
@@ -195,17 +448,25 @@ try
foreach($initial in @($versions[0], $versions[2]))
{
$channel = $initial.channel
$initialPackageId = $packageId + '-' + $channel
$env:K22_UITEST_CHANNEL = $channel
$install = Join-Path $outputRoot "installed-$channel"
if(Test-Path -LiteralPath $install) { throw "Existing installation requires investigation: $install" }
$setup = Join-Path $fixtureRoot "$($initial.version)/$packageId-$channel-Setup.exe"
$setup = Join-Path $fixtureRoot "$($initial.version)/$initialPackageId-$channel-Setup.exe"
$data = Join-Path $outputRoot "data-$channel"
New-Item -ItemType Directory -Path $data -Force | Out-Null
'{"ShowSplashScreen":false,"CheckForUpdatesOnStartup":false}' | Set-Content -LiteralPath (Join-Path $data 'settings.json')
[Environment]::SetEnvironmentVariable($dataVariable, $data)
[Environment]::SetEnvironmentVariable($devDataVariable, $data)
$defaultChoices = Get-SmokeDefaultChoiceSnapshot | ConvertTo-Json -Depth 3 -Compress
$registration = $null
Invoke-SmokeProcess -File $setup -Arguments @('--silent', '--installto', $install, '--log', (Join-Path $outputRoot "install-$channel.log"))
try
{
$currentExe = Join-Path $install "current/$mainExe"
$registration = Get-SmokeFileRegistration -Exe $currentExe -Channel $channel
if($defaultChoices -cne (Get-SmokeDefaultChoiceSnapshot | ConvertTo-Json -Depth 3 -Compress)) { throw 'Installation changed existing Windows default choices.' }
$registration | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $outputRoot "$channel-registration-before.json")
$installedProbe = Join-Path $install "current/$probeExe"
$before = Get-SmokeResult -Exe $installedProbe -ResultPath (Join-Path $outputRoot "$channel-before.json") -Mode 'apply'
if(-not $before.Installed -or -not $before.AppKitSupported -or $before.IsPortable -or $before.Version -ne $initial.version -or $before.AppKitVersion -ne $initial.version -or $before.Channel -ne $channel -or $before.UpdateVersion -ne $initial.target -or $before.IncludePrereleases -ne ($channel -eq 'dev')) { throw "Incorrect $channel installed update offer." }
@@ -233,8 +494,13 @@ try
Stop-SmokeApp -Exe $currentExe -Confirm:$false
$after = Get-SmokeResult -Exe $installedProbe -ResultPath (Join-Path $outputRoot "$channel-after.json")
if($after.Version -ne $initial.target -or $after.Channel -ne $channel -or $null -ne $after.UpdateVersion -or $after.AppKitVersion -ne $initial.target) { throw 'Post-upgrade identity or channel is incorrect.' }
$updatedRegistration = Get-SmokeFileRegistration -Exe $currentExe -Channel $channel
if($updatedRegistration.ProgId -cne $registration.ProgId) { throw 'Update changed the stable file-type owner identity.' }
if($defaultChoices -cne (Get-SmokeDefaultChoiceSnapshot | ConvertTo-Json -Depth 3 -Compress)) { throw 'Update changed existing Windows default choices.' }
$updatedRegistration | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $outputRoot "$channel-registration-after.json")
$env:K22_UITEST_INSTALLED = '1'
$env:K22_UITEST_EXE = $currentExe
Invoke-SmokeProcess -File 'dotnet' -Arguments @('test', "$repositoryRoot/tests/$name.UiTests", '--no-build', '--filter', 'FullyQualifiedName!~CheckForUpdates_InALooseBuild', '--logger', "trx;LogFileName=installed-$channel-ui.trx", '--results-directory', $outputRoot) -TimeoutSeconds 180
Invoke-SmokeProcess -File 'dotnet' -Arguments @('test', "$repositoryRoot/tests/$name.UiTests", '--no-build', '--filter', 'FullyQualifiedName!~CheckForUpdates_InALooseBuild', '--logger', 'console;verbosity=normal', '--logger', "trx;LogFileName=installed-$channel-ui.trx", '--results-directory', $outputRoot) -TimeoutSeconds 600
$records.Add(@{ kind = 'installed'; channel = $channel; before = $before; after = $after; restarted = $true; appSha256 = $installedHash })
}
finally
@@ -243,22 +509,30 @@ try
$updater = Join-Path $install 'Update.exe'
if(Test-Path -LiteralPath $updater)
{
$manifestPath = Join-Path $install 'current/sq.version'
$manifestValue = [xml](Get-Content -LiteralPath $manifestPath -Raw)
if(-not $install.StartsWith($allowedRoot, [StringComparison]::OrdinalIgnoreCase) -or $manifestValue.package.metadata.id -ne $packageId) { throw 'Refusing to uninstall an unexpected application.' }
Assert-SmokeInstallationOwner -Install $install -PackageIdentity $initialPackageId -ArtifactRoot $allowedRoot
Invoke-SmokeProcess -File $updater -Arguments @('--silent', '--rootDir', $install, 'uninstall')
$deadline = [DateTime]::UtcNow.AddSeconds(10)
while((Test-Path -LiteralPath $install) -and [DateTime]::UtcNow -lt $deadline) { Start-Sleep -Milliseconds 200 }
if(Test-Path -LiteralPath $install) { throw "Smoke installation was not removed: $install" }
if(Test-Path -LiteralPath "HKCU:/Software/Microsoft/Windows/CurrentVersion/Uninstall/$packageId") { throw 'Smoke uninstall registry entry remains.' }
if(Test-Path -LiteralPath "HKCU:/Software/Microsoft/Windows/CurrentVersion/Uninstall/$initialPackageId") { throw 'Smoke uninstall registry entry remains.' }
if($null -ne $registration)
{
Assert-SmokeFileRegistrationRemoved -Registration $registration
if($defaultChoices -cne (Get-SmokeDefaultChoiceSnapshot | ConvertTo-Json -Depth 3 -Compress)) { throw 'Uninstall changed existing Windows default choices.' }
[ordered]@{ ProgId = $registration.ProgId; Removed = $true; ExistingDefaultsPreserved = $true } | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $outputRoot "$channel-registration-removed.json")
}
}
}
}
Test-SmokeCoexistingChannel
ConvertTo-Json -InputObject @($records) -Depth 10 | Set-Content -LiteralPath (Join-Path $outputRoot 'results.json')
Write-Output "Installed stable/dev smoke passed: $outputRoot/results.json"
}
finally
{
[Environment]::SetEnvironmentVariable($dataVariable, $oldDataDirectory)
[Environment]::SetEnvironmentVariable($devDataVariable, $oldDevDataDirectory)
$env:K22_UITEST_CHANNEL = $oldUiChannel
$env:K22_UITEST_EXE = $oldUiExe
$env:K22_UITEST_INSTALLED = $oldUiInstalled
}
+22 -9
View File
@@ -2,30 +2,43 @@
Build a Windows counterpart to [Darin Kelkhoff's markdownViewr](https://github.com/darinkelkhoff/markdownViewr), using the KofTwentyTwo AppKit foundations and standards. The agreed display name is MarkdownViewr, repository MarkdownViewr.Windows, and permanent application ID markdownviewr-windows.
The current tree is a generated, building AppKit scaffold. Its greeting screen remains a sample; it is not yet a Markdown viewer. The following features are requirements, not implemented claims.
The reader implements passive GFM, bounded local images, save/replacement/deletion recovery, the original20 themes and editable overrides, frontmatter, outline/collapse, literal synchronized source, paired find, vim commands, named editors and per-window zoom. Clean baseline `300e145` / CI38126078284 passes52 native/published and51 installed stable/dev tests with no failures/skips, including original3 native printing/PDF cases. Both actual AppKit update/apply/restart probes pass; fixtures are unsigned.
Full `bf4fcdc` / CI38128311242 passes54 native/published and53 installed stable/dev tests, zero skips: all5 print cases, real font/padding/width/border/background/image output and preserved source/find/reading state. Twenty-four generated PDFs and both actual AppKit apply/restart probes are retained; fixtures remain unsigned. Multiple native document windows, modern multiple-file picking, bounded private recent files and sibling-safe settings merging are implemented locally with376 core tests/100% measured lines; hosted lifecycle acceptance remains pending. Windows associations/activation and the remaining reference features below are still required. This is not a completed port.
Windows file registration and activation are now implemented locally (ADR-0005):
installed channel/executable identities, preserved defaults, SDK primary selection
and current-user bounded literal-path delivery. Nine real isolated boundary tests
pass, and the corrected entry point publishes/packs with all 543 published files verified
byte-for-byte in both archives. Real Explorer/installed lifecycle and two prepared
secondary-process/profile/duplicate/watcher cases remain unverified; this does
not complete the port or establish production signatures.
## Reference and feature scope
Credit the original macOS app and Darin Kelkhoff in About and public documentation, linking to [his repository](https://github.com/darinkelkhoff/markdownViewr) and [website](https://kelkhoff.com/); retain distributed MIT notices. Windows issues/support must remain in the Windows repository.
Reference source inspected at commit `6d08bc9d27d7d7b2c2fa95bb282da330b8cf9021`. Preserve Darin's MIT copyright/license when adapting his rendering resources or other code. macOS SwiftUI/WebKit and Sparkle integrations require Windows equivalents.
| Area | Required Windows behavior |
| --- | --- |
| Documents | Open Markdown through a picker and Windows file activation; multiple document windows; recent files; clear missing/unreadable-file errors |
| Rendering | GitHub Flavored Markdown, tables, tasks, strikethrough, fenced code, local images and links |
| Documents | Open `.md`, `.markdown`, `.mdown`, `.mkd` and plain text through a multiple-file picker and Windows activation; independent document windows; recent files/clear history; clear missing/unreadable-file errors |
| Rendering | GitHub Flavored Markdown, tables, tasks, strikethrough, fenced code, local images/links; independently configurable highlight, superscript, subscript and underline extensions |
| Reload | Watch saves, including atomic replacements; debounce changes, preserve scroll, report deletion/recovery |
| Themes | Built-in themes, editable user themes, custom CSS, code font/size and persistent settings |
| Navigation | Resizable table of contents, depth control, heading jumps and collapsible sections |
| Source | Optional source pane, synchronized headings/scroll and collapse state |
| Themes | 20 built-ins; editable body/heading/code fonts, sizes and palette; theme/global CSS; content width; theme order, enable/disable/delete/restore and cycling; JSON import/export with overwrite/keep-both/skip conflicts; persistent settings |
| Navigation | Resizable table of contents, depth control, configurable wrapping or ellipsis/full-title tooltip, optional heading-depth markers, heading jumps, collapsible sections and per-window font zoom (30–500%, Ctrl+plus/minus/0) |
| Source | Optional source pane, synchronized headings/scroll and collapse state; theme-aware syntax highlighting; draggable split opening at50/50 (#16/#17) |
| Find | Match counts, next/previous matches, search in both panes and vim-style shortcuts |
| Keys | j/k, Ctrl+d/u, gg/G, ]]/[[, /, n/N; Windows Ctrl-based equivalents for native commands |
| Frontmatter | Hide YAML, display metadata or render it as Markdown |
| Editor | Configurable external editor with safe literal file arguments |
| Print | Native printing, theme/layout options and restoration of screen state afterwards |
| Foundation | AppKit About, activity log, theme/settings, crash handling, stable/dev updates, tests and release controls |
| Print | Native printing, theme/layout options, readable PDF export with optional PDF/folder opening, and restoration of screen state afterwards (#22/#23) |
| Toolbar/settings | Mac toolbar action parity (#18), native Settings pages for General, themes, editors, Printing defaults and Advanced startup options (#21); deliberate Apply/Restore for print/CSS drafts, independent pane defaults and shared saved preferences |
| Foundation | AppKit About with original-author credit, repository/issues/community Help links, activity log, crash handling, stable/dev updates, tests and release controls |
## Proposed implementation for discussion
Use a WinUI 3 shell with a WebView2 document surface, a pure .NET core for document/rendering state, and thin Windows adapters for file activation, watching, editor launch and printing. Evaluate a maintained .NET Markdown parser against the original's actual output before choosing it. Reuse compatible original HTML/CSS/navigation resources where useful, retaining attribution and replacing the macOS message bridge.
Use a WinUI 3 shell with a WebView2 document surface, a pure .NET core for document/rendering state, and thin Windows adapters for file activation, watching, editor launch and printing. Markdig and HtmlSanitizer implement the first GFM and passive-markup boundary; see ADR-0002. Reuse compatible original HTML/CSS/navigation resources where useful, retaining attribution and replacing the macOS message bridge.
Treat document Markdown, HTML, images, CSS, links and web messages as untrusted. Document content must not execute arbitrary script or gain access to native host commands. Local-resource access must remain bounded to explicitly allowed document resources; remote navigation and external editor launch require deliberate user actions. Define and test the renderer policy before loading user files.
+2 -3
View File
@@ -1,6 +1,5 @@
# Releasing MarkdownViewr
Releases follow the
[KofTwentyTwo release standard](https://github.com/KofTwentyTwo/standards/blob/main/standards/releases.md).
Pushing a protected `v*` tag on `main` runs [`.github/workflows/release.yml`](../.github/workflows/release.yml),
@@ -8,7 +7,7 @@ which calls the shared `release-velopack.yml` workflow in KofTwentyTwo/standards
## Workflow baseline
Shared workflows are pinned to published standards v0.1.2. The release caller checks main ancestry and reruns build, coverage, formatting, UI tests, security scans and CodeQL before publication.
The signing release builder is pinned to standards v1.1.0 (`93b1d6163a4c466ae443e98b62786fc4d80d33bd`); other shared gates retain v0.1.2. The release caller checks main ancestry and reruns build, coverage, formatting, UI tests, security scans and CodeQL before publication.
The AppKit dependency locks now match the published 0.1.0 packages. Before changing any package version, regenerate the affected locks and verify a locked restore with a clean package cache.
## Versioning and channels
@@ -75,7 +74,7 @@ payloads, using final-byte hashes and verified provenance. Follow the
```
4. Watch the **release** workflow, then verify the release as described in the
[README](../README.md#verifying-a-release).
[developer guide](../DEVELOPERS.md#releases-and-verification).
5. Open a pull request that bumps `VersionPrefix` in `Directory.Build.props`.
## When something fails
+1743 -6
View File
File diff suppressed because it is too large. Load diff
+226 -11
View File
@@ -1,13 +1,228 @@
# Development Tasks
- [x] Generate a GUI-only application from the tested AppKit starter using the agreed identity.
- [x] Restore published AppKit 0.1.0 packages; build with zero warnings; pass 20 existing core tests and the 100% coverage gate.
- [x] Inspect pinned markdownViewr source and capture the full Windows feature requirements in PRODUCT.md.
- [ ] Discuss the desired first release and Windows-specific behavior with the owner.
- [ ] Select and document the renderer and untrusted-document security policy.
- [ ] Replace the sample greeting with file opening and rendered Markdown.
- [ ] Implement and test all document, theme, navigation, source, find, frontmatter, editor and printing requirements in PRODUCT.md.
- [ ] Update the threat model, attribution, screenshots, user documentation and agent guidance as features land.
- [ ] Establish hosted repository gates and standards inventory; resolve inherited analyzer instability without suppressing errors.
- [ ] Adopt the reviewed/published structured AppKit API and signing builder when available.
- [ ] Verify actual signed stable/dev installers and the exact shipped bytes before any release publication.
## Current release checklist
- [x] Correct the disabled-editor tooltip exception; accepted application code321816c.
- [x] Final signing caller b1dc874 / CI38276579007 passes all hosted checks, including published111 and stable/dev110 acceptance each, zero failures/skips.
- [x] Verify actual update/restart, registrations, preserved defaults, and channel coexistence receipts.
- [x] Publish/verify standards1.1.0 signing builder and merge owner-reviewed inventory PR36.
- [x] Verify Azure configuration and representative local final signing/distribution proof; existing gclo configuration preserved.
- [x] Register Windows OpenSSF project15417 and record its real progress badge; passing assessment remains incomplete.
- [x] Prepare user-first README, real light/source screenshots, user guide, DEVELOPERS.md, and current support/contribution links (#34).
- [x] Verify final documentation: six Markdown files pass lint, all33 local links resolve, static conformance has no FAIL; clear-desktop screenshots are inspected.
- [ ] Commit/push the documentation candidate and pass required exact-head hosted checks.
- [ ] Obtain the genuine final-head maintainer review record; apply/read back only the planned Actions-bound fuzzing context.
- [ ] Merge the application and complete release checklist#33.
- [ ] Prove production CI OIDC signing, then publish1.0.0-beta.1/dev and1.0.0/stable.
- [ ] Verify downloaded final signatures/publisher/trust/timestamps, hashes, SBOMs, and attestations.
- [ ] Complete the evidence-backed OpenSSF provider assessment with the maintainer's actual operating-practice attestations.
The historical checkpoints below retain previous evidence and superseded tasks.
Original owner preview processes/profiles must remain untouched.
## Historical checkpoints
- [x] Exact9d5c0d1 full native106/106 and focused26/26 pass, zero skips; retain actual TRX/Settings screenshots and logs.
- [x] Correct observed offscreen Settings placement by centering size/position in the actual monitor work area; zero-warning build/format pass.
- [ ] Verify whole-window visibility and inspect complete Settings screenshots on pushedc3c735e / CI38190828814; preserve its live execution.
- [x] Retain terminal1bfab22 native102/106, focused23/26 and published600-second/no-TRX evidence; same four failures, installed lifecycle not reached.
- [x] Retain exact1bfab22 focused23/26 TRX; long-title tooltip/physical activation passes, offscreen Nord/future-theme lookup and source alignment fail.
- [x] Prepare native scrolling of the owned virtualized theme list to realize the same exact IDs before unchanged selection/assertions/deadline.
- [x] Exact9d5c0d1 focused26/26/native106/106 prove offscreen palette/default, existing future-schema/export and repeat-heading correction; zero skips.
- [x] Restore trusted heading Tag on the new Contents content Grid so repeat ItemClick can identify the selected row; local zero-warning build/format pass.
- [x] Exact9d5c0d1 unchanged repeated physical Contents/source alignment passes; pointer diagnostics use client coordinates.
- [x] Prepare accessible native theme palette previews, state labels and explicit default radio selection with unchanged copied-draft/save semantics.
- [x] Exact9d5c0d1 actual palette pixels/default/save/reopen passes and Settings screenshot inspected; new placement correction remains pending.
- [x] Retain terminal b2ecc8c focused23/24/native101/102 and published600-second timeout/no TRX; only source alignment fails in completed suites.
- [x] Prepare fixture-only closure of all owned windows before unchanged FlaUI close/kill fallback; preserve cases/assertions/deadlines.
- [x] Exact9d5c0d1 published106/106 and installed stable/dev105/105 pass with zero skips, including source/tooltip/default status; retain actual TRXs and coexistence JSON.
- [x] Prepare truthful General effective `.md` default status with bounded read-only Windows querying and automatic/manual refresh;7 actual read-only/isolated adapter tests pass.
- [ ] Verify actual General status/refresh and user-selected OS chooser/Explorer behavior on an installed build; registration alone is not default selection.
- [x] Exactb2ecc8c focused23/24: both Printing cases/raw-inline/picker/Contents options pass, zero skips; retain remaining physical alignment failure.
- [x] Exact9d5c0d1 trimmed tooltip/physical activation and unchanged source alignment pass in focused/full native suites.
- [x] Retain terminal b5e519e published98/100 TRX: same raw-fold/source-click failures, zero skips; installed lifecycle phase not reached.
- [x] Prepare shared native Printing Settings options with Apply/Restore, validated checked saves and sibling draft preservation; zero-warning Release/format pass.
- [ ] Execute both new Printing defaults/draft acceptance cases plus every existing Print/PDF/pixel case on the exact next hosted head; inspect actual Settings screenshots.
- [x] Reproduce exact raw inline/HtmlBlock closing-boundary failure and correct token provenance;598 core/100% measured lines pass.
- [x] Prepare native tooltip placement/non-hit-testing and exact owned picker enumeration corrections from retained b5e519e evidence.
- [ ] Verify corrected raw fold, physical source/Contents clicks and picker cancellation on the next exact hosted head; retain every assertion/deadline.
- [x] Exact53755ab: published99/99, installed stable/dev98/98 each; actual five-extension registration before/after update and owned uninstall/default preservation pass.
- [x] Exact53755ab: simultaneous stable/dev profiles/processes, isolated actual updates and stable uninstall preserving dev pass; retain coexisting-channels.json and TRXs.
- [x] Prepare native General Contents wrapping/depth-marker controls with checked sibling-safe persistence;593 core/100% measured lines and zero-warning Release pass.
- [ ] Execute the actual long-title/sibling/persistence/restoration test and inspect Settings screenshots on its exact pushed head.
- [x] Review all eight Mac screenshots and record verified Settings gaps in issue #21 without private content.
- [ ] Implement/accept the native Windows Settings organization, General default-viewer status, Contents options, theme previews/defaults and Printing page.
- [x] Reproduce/correct multiline inline raw-heading fold loss;592 core tests/100% measured lines and zero-warning Release pass.
- [ ] Verify the updated inline raw-heading runtime fixture and complete distributed acceptance on its exact pushed head.
- [x] Exact6374611: native97/98 and published97/98, focused19/20, zero skips; actual sustained colors/hover/focus and outline pass.
- [x] Exact9994eda: offscreen native geometry correction passes focused20/20 and full native98/98; original focus/both-direction glyph/wheel assertions unchanged.
- [x] Exact9994eda: published98/98 and installed stable/dev97/97 each, real update/apply/restart and target hashes pass; zero skips.
- [x] Exact764c7ac: retain focused20/21, native98/99 and published98/99; only the new raw multiline-heading fold fails, zero skips. Installed lifecycle phase is not reached.
- [x] Reproduce multiline raw-header loss in core tests and correct parser token end spans;589 core/100% measured lines, zero-warning Release/format pass.
- [x] Exactbd3d72a: raw-HTML correction passes focused21/21 and native99/99; retain published process log with two real initial print-fixture failures and600-second outer timeout/no TRX.
- [ ] Diagnose actual published print startup failures using owned logs/tree/screenshots and per-case progress; preserve deadlines/assertions.
- [x] Exact9d5c0d1 full published/installed suites and actual registration lifecycle/coexistence readbacks pass; unsigned fixtures are not production-signature proof.
- [x] Generate and publish the GUI-only AppKit application with the approved identity.
- [x] Configure/read back repository protections and establish the initial hosted CI baseline.
- [x] Inspect pinned markdownViewr source and record the full parity scope in PRODUCT.md.
- [x] Implement GFM parsing, passive HTML sanitation and the isolated resource policy (ADR-0002).
- [x] Replace the greeting/crash controls with file opening, rendered Markdown and manual reload.
- [x] Test physical image containment, including junction escapes, and input size limits.
- [x] Update the threat model, renderer attributions and shipped library notices.
- [x] Verify first-reader hosted browser acceptance: 13 native/UI tests passed on f1756d2.
- [x] Verify live reload/browser network scenarios and retain actual theme screenshots on head 77dd04d.
- [x] Owner authorizes signed stable1.0/dev releases with the requested Windows-specific controls/defaults; publication still requires all gates.
- [x] Implement/test directory watching and validated relative viewport state for saves/replacements/deletion recovery.
- [x] Complete actual browser save/lock/replacement/deletion/scroll acceptance (issue #6): hosted native/UI 26/26; published 26/26; installed stable/dev 25/25 each.
- [x] Port Darin's 20 built-in themes and persistent document selection; validate schema, font escaping and invariant CSS.
- [x] Verify hosted built-in theme selection and actual painted colors on 77dd04d.
- [x] Implement persisted editable theme overrides/custom names, color/code-font/CSS controls and built-in restoration.
- [x] Complete hosted theme editor/validation/restoration acceptance (issue #7): CI 38102867556 passed; size 17 edit also passes in 29/30 later UI tests.
- [x] Implement persistent resizable TOC/depth, safe heading/local-fragment jumps and independent nested section collapse (issue #9).
- [x] Verify hosted heading jumps, independent nested collapse and theme/reload state on 5c3988e (31/33 UI pass).
- [x] Verify physical sidebar/source resize and frontmatter modes: CI 38106991467, 34 native/published and 33 per installed channel, no skips.
- [x] Implement literal read-only source pane, saved visibility/width and shared committed reload snapshots (issue #10).
- [x] Verify source visibility/read-only/literal/reload and physical resize in native, published and both installed builds on 4bf0b81.
- [x] Implement paired native browser/source find with bounded literal terms, shared counts, wraparound and reload guards (issue #10).
- [x] Verify paired find and repeated local-fragment reveal on 5a9af63: 41 native/published and 40 per installed channel, zero skips.
- [x] Implement all required vim reading/find/heading commands, timed prefixes and native focus/modifier guards (issue #10).
- [x] Verify physical vim viewport, collapsed-heading and query-typing acceptance on 6bf125d: CI 38112298176, 44 native/published and 43 per installed channel, zero skips.
- [x] Map sanitized surviving headings to original parser-derived source offsets (Setext/fences/raw HTML/frontmatter/duplicates); source UI synchronization remains pending.
- [x] Implement literal nested source folding and bidirectional heading-based scroll synchronization (issue #10), preserving canonical find/reload snapshots.
- [x] Verify actual native/browser heading positions, physical source scrolling and fold/find/reload restoration: `2639e2d` / CI38121762124, published49/49 and installed stable/dev48/48, no skips.
- [ ] Audit find/source collapse parity against the pinned reference, including multiline and same-line raw HTML headings (issue #10).
- [x] Implement and core-test hidden/metadata/Markdown frontmatter modes, with persisted native controls and attribution.
- [x] Verify actual code-font size 17 editing in hosted UI runs.
- [x] Verify actual hosted frontmatter mode switching on 4bf0b81 (including both installed channels).
- [x] Implement bounded persisted editor selection and deliberate native launch using one literal process argument (issue #11).
- [x] Verify actual editor fixture launch, validation/cancel and missing-executable acceptance on 5a9af63 in native/published/installed builds.
- [x] Audit and implement named editor lists, stable migration, selection/reordering/removal, cancel-safe drafts and folder-opening parity (issue #11).
- [x] Verify actual editor-list/menu order, file/folder probe arguments, cancellation and final removal: `2639e2d` native/published/installed stable/dev, no skips.
- [x] Implement native menu/toolbar per-window font zoom, 30–500% bounds and guarded Ctrl+plus/minus/0 commands (issue #13); 330 core tests/100% measured lines.
- [ ] Verify actual zoom glyph/key/reload/theme/find/collapse behavior in native, published and both installed channels.
- [x] Implement print-only themes/options, native Windows print UI and explicit PDF save (issue #12); core363/100% lines; no new renderer dependency.
- [x] Verify original3 focused native print cases on ec5f98f/38125751638: both cancels, real default/themed PDF pixels and unchanged actual glyph/view/source/collapse state.
- [x] Implement actual PDF font/padding/width/border comparisons and successful-export paired-find preservation; d0a225d native/published width/borders pass, zoom wait fails before font/padding output. Diagnostic6e17674 confirms rounded UIA metrics; corrected case measures precise font ratios in actual PDFs.
- [x] Verify clean original3 print cases in full300e145: native/published52 each, installed stable/dev51 each, zero failures/skips; real PDFs and update apply/restart retained.
- [x] Verify corrected native54/54 on bf4fcdc / CI38128311242: actual PDF font/padding/paired-find plus original cases, zero failures/skips.
- [x] Verify all5 print cases in full bf4fcdc / CI38128311242: native/published54, installed stable/dev53 each, zero failures/skips;24 actual PDFs/48 PNGs/TRXs retained, update apply/restart passes both channels.
- [x] Implement native independent document windows, multiple-file picker/all reference extensions and bounded private recent files/clear; source-generated sibling-safe preference merging. Local376 core/100% lines and zero-warning build pass.
- [ ] Verify actual hosted Ctrl+N/Ctrl+W, two-window zoom/watching, actual multiple-file picker/cancel, recent duplicate/missing/clear/stale-save and File/Exit acceptance.
- [x] Implement Windows file registrations and cross-process activation with channel/install/profile identities, quoted commands and preserved defaults;9 actual isolated adapter tests pass.
- [ ] Verify Explorer Open With, installed registration/update/uninstall and actual secondary-app/profile/duplicate/watcher behavior in all distributed builds (issue #14).
- [x] #14: Implement Settings → Advanced → Choose default file types. Read-only install ownership checks target this exact registered application on Windows 11 23H2+; older versions open general Default Apps. Portable previews show installation guidance; `.txt` remains optional. Eleven isolated registration/activation tests pass.
- [ ] #14: Verify the actual installed Settings action, Windows Default Apps selection and Explorer double-click for Markdown types; confirm optional `.txt` remains unchanged, stable/dev identities and older-Windows guidance. New native page case compiles, not yet run.
- [x] Expose all body/heading/code typography controls, including heading levels1–6 and line height, with copied core edit validation.18 focused theme regressions; combined426 core tests/100% lines, zero-warning full build/format pass. Unsigned worktree only.
- [ ] Verify real hosted complete typography save/heading-body-code glyph growth/cancel acceptance; new case compiles, not run.
- [x] Implement shared global CSS and bounded persisted content width, copied cancel-safe drafts, sibling broadcasts and separate print styles.22 focused regressions; combined448 core/100% measured lines, zero-warning build/format pass. Unsigned worktree only.
- [ ] Verify real hosted width wrapping/global CSS/theme changes/PDF isolation/cancel and two-reader broadcast/stale local save; two new cases compile, not run.
- [x] Implement copied native theme order/enable/delete/restore and shared selection, saved menu order and guarded Ctrl+Shift+Up/Down cycling. Restoration preserves edits as collision-safe custom copies.23 focused cases; combined471 core tests/100% measured lines, zero-warning build/format pass; local worktree only.
- [ ] Verify actual hosted menu order/disabled choices/physical cycle/focus/cancel, sibling deletion/restoration and updated retained-override restoration case. Two new cases compile, not run.
- [x] Implement bounded multi-file JSON import, explicit overwrite/keep-both/skip/newer-schema choices, metadata/version-preserving disabled-theme export and native manager New/Copy/Edit/Use selected with copied cross-dialog drafts.46 focused regressions; combined517 core/100% measured lines and zero-warning full build pass; local worktree only.
- [ ] Verify all six new hosted conflict/batch/future/disabled-export/new-copy-edit-cancel cases in native, published and installed builds; headless discovery finds69 total cases, runtime not executed.
- [ ] Audit original global/per-theme CSS variable/DOM selector compatibility; retain full reference stylesheet behavior.
- [x] Implement independent saved highlight/superscript/subscript/underline options and native controls; existing parser/sanitizer, exact source offsets, code/newline/whitespace guards and theme palette. Local408 core tests/100% lines and full build/format pass; unsigned worktree only.
- [ ] Verify hosted native extension glyphs/paint/toggles/persistence/source/reload in all distributed builds; one real acceptance case is prepared, not run.
- [ ] Audit remaining extension edge cases against raw authored HTML and complete local image/link/render fidelity.
- [x] Verify the all-rule two-pass PSScriptAnalyzer mitigation on scripts 38104988748 and 38105483489; closes #5 only on reviewed merge.
- [ ] Verify hosted checks on each actual implementation head.
- [ ] Finish standards inventory and reviewed/published AppKit/signing API adoption when available.
- [ ] Verify actual signed stable/dev installers and exact shipped bytes before release publication.
## Native and IPC follow-up - 2026-10-11
- [x] Exact9fdfc2d focused native acceptance14/15,zero skips: source reopening,
highlighting, four-file import and repeated-heading navigation now pass.
- [x] Reproduce the disconnected-peer failure deterministically and correct
owned native cleanup: actual pre-fix10-second connection timeout, then four
cases pass and25runs/100tests pass after fixing Broken-state disconnection.
- [ ] Verify current full/published/installed results and the IPC correction on
the exact next pushed head; do not substitute local stress for distributed proof.
- [ ] Diagnose source focus/scroll alignment using the retained numeric probe,
preserve exact native assertions, and remove [DEBUG-source-coordinates]
instrumentation before release.
- [x] Add/read back the real reader-regressions required check on live main,
preserving every previous protection and GitHub Actions binding.
## Previous acceptance checkpoint - 2026-10-11
- [x] Exact8abbd4d hosted build/core/format pass; full native93/97,
focused10/12,zero skips. Retained native/published and real PDF artifacts
are documented in SESSION-STATE.md.
- [ ] Verify the next native Toggle/menu, four-file picker and heading-inset
correction in all distributed builds; retain exact source-color and source
focus/scroll failures. The focused job includes every remaining native case.
- [ ] Complete current published and installed stable/dev acceptance;8abbd4d
again exceeds600seconds during published tests before installed outcomes.
Investigate the reproduced disconnected-pipe timeout; local75-case stress
did not reproduce it. Preserve owner preview55472 and all signing gates.
## Owner follow-ups - 2026-10-11
- [ ] #22: Fix the system print handoff and widen print options.
- [ ] #23: Diagnose PDF export and offer success-only open PDF/folder actions. Readable-output verification and options implemented locally; runtime acceptance pending.
- [ ] #19: Credit Darin Kelkhoff's original Mac app in About and documentation, with repository/site links. Implemented locally; actual About acceptance pending.
- [ ] #20: Match gclo Help commands and carry defaults into AppKit samples and the starter. Implemented locally; builds/generation/setup tests pass, native acceptance/signing/push pending.
- [x] #21: Replace owner-rejected modal tabs with a standard resizable WinUI Settings window, left navigation, native controls, immediate checked width preferences and explicit CSS apply. Theme/editor dialogs and pickers belong to Settings.
- [ ] #21: Verify native window resizing/navigation, immediate preferences, CSS apply/discard and owned theme/editor/picker dialogs in distributed builds.
- [x] #16: Implement native passive source highlighting, document/shell colors, high contrast and exact plain copying.570 core tests and100% measured lines pass.
- [ ] #16: Run real native color/mode/palette/selection/clipboard and existing find/fold/scroll/reload acceptance in distributed builds.
- [x] #17: Implement50/50 source opening/reopening, proportional mouse resizing and focused Left/Right/Home controls;21 core layout regressions pass.
- [ ] #17: Verify actual mouse/keyboard/outline/DPI/window-resize/reopening geometry in distributed builds.
- [x] #18: Implement reference-order native toolbar with dynamic overflow, shared reading handlers, H1–H6, enabled themes, valid editor/direct/choices and primary Print; Theme uses the native Color icon. Broadcast saved editor choices to all readers; build/format pass.
- [ ] #18: Verify four prepared actual toolbar reading/menu/editor/Print/narrow/sibling cases in native, published and both installed builds.
- [ ] #6: Verify automatic external-save refresh or a clear stale/reload notification in the current build. Native watcher6/6 passes; explicit fallback/new locked-save UI case implemented, actual acceptance pending.
- [x] #24: Move Darin attribution into About Contributions; link23 runtime/platform and29 development entries verified against restored package metadata; preserve11 distinct packaged license/notice texts.
- [ ] #24: Verify actual About scrolling, keyboard-accessible project/license links and Contributions placement in distributed builds.
- [x] #25: Add gclo-style native Advanced splash toggle/duration (500–30000 ms, 250 ms steps), startup update preference, checked saves and sibling broadcasts using the published AppKit splash.
- [ ] #25: Verify actual startup duration/enabled/disabled and shared preference persistence in distributed acceptance.
- [ ] #18: Review the tightened toolbar row, control alignment and grouped commands against the owner screenshot; geometry assertions are prepared.
## Active 1.0 release queue - 2026-10-11
Full stable1.0 and isolated development releases are authorized. Local implementation
checks below do not substitute for current native/distributed acceptance.
- [x] #27: Reproduce source-hover color loss and fix native foreground state assignments; exact hosted pointer/focus/selection regression passes on0ed608e/CI38166230860.
- [ ] #27: Verify all source color/theme/reload/copy/scroll behavior in current published and installed editions; full acceptance remains open.
- [x] #18: Remove the duplicate toolbar depth selector and label Contents/Print; retain TOC depth.
- [ ] #18: Verify current toolbar alignment, overflow and narrow-window behavior.
- [x] #21: Add independent new-window Contents/Source defaults, both off; core merge/copy regressions pass.
- [ ] #21: Verify persisted defaults and unchanged existing windows through actual Settings input.
- [x] #21: Move long theme/editor forms into native Settings pages with Back/Save/Cancel; preserve copied drafts/import conflicts/native pickers.
- [ ] #21: Verify the current Settings pages, draft cancellation/validation and imports at normal/narrow sizes.
- [x] #26: Add loaded-handle modification time, stale/reload status and native Open folder; real metadata adapter regression passes.
- [ ] #26: Verify actual footer changes during locked/replaced/missing files and independent windows.
- [x] #28: Add accessible/native delayed disabled-editor explanation with the actual unavailable reason and Settings guidance.
- [ ] #28: Verify actual disabled hover, mouse exit, state changes and keyboard accessibility.
- [x] #29: Generate distinct navy Markdown/reading mark through AppKit BrandTool; visually inspect the256px frame and validate all nine ICO sizes.
- [ ] #29: Verify icon in current distributed app/window/taskbar/splash and obtain owner design feedback.
- [x] #24: Limit Components to shipped open-source credits/project/license links; preserve full audit catalog and notices, with Darin in Contributions.
- [ ] #24: Verify current native About layout, Contributions and all credit links.
- [x] Port pinned document CSS/container/scroll/print semantics locally. Current combined582 core tests/100% lines,25 adapter tests,zero-warning Release/format/publish pass.
- [x] Verify immutable upstream template/CSS-help blobs and all47 shared screen CSS selector declarations; document native pane-CSS compatibility boundary in reference-fidelity-audit.md.
- [ ] Complete remaining rendering/native/distributed fidelity acceptance and owner design review; CSS declaration comparison does not establish painted-output or arbitrary native pane-CSS parity.
- [ ] Pass hosted CI on the actual current implementation; retain native/published/installed TRXs, screenshots and real PDFs.
- [x] Implement gclo-style separate stable/dev package IDs and installed-channel title/data identity;586 core tests/100% measured lines pass.
- [x] Prepare simultaneous owned stable/dev windows/profiles/registrations, real AppKit updates with a live unchanged sibling, and stable uninstall preserving dev;64 tooling tests/all configured analyzer rules pass.
- [x] Exact53755ab actual simultaneous windows/profiles/isolated updates/uninstall phase passes; current-head distributed acceptance and OS chooser remain separate requirements.
- [ ] Diagnose dbda6d4 focused17/20 failures with retained native color/focus/hidden-section evidence; preserve exact assertions and remove temporary debug probes before release.
- [ ] Adopt the reviewed/published owned-file signing builder; verify actual Azure resource/profile/publisher and repository-bound OIDC/profile-signer permission.
- [ ] Obtain genuine current-head maintainer review acknowledgement, complete required checks and merge.
- [ ] Publish signed stable1.0/dev releases with verification of exact distributed artifacts, timestamp/publisher/checksums and installed update acceptance.
@@ -0,0 +1,142 @@
# ADR-0002: GFM rendering inside an isolated WebView2 document surface
- **Status:** Proposed; implemented on the viewer topic branch, pending review
- **Date:** 2026-10-10
- **Decider:** Project maintainer
- **Tracking:** [reader milestone #4](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/issues/4)
## Context
Darin's document layout needs HTML/CSS, GFM tables, tasks and useful raw HTML.
Documents can contain hostile markup, resource paths and links. A Markdown parser
alone does not provide a native application security boundary.
## Decision
Use Markdig 1.4.0 (BSD-2-Clause) for GFM and HtmlSanitizer 9.2.1039 (MIT) for
an explicit passive-markup allowlist. AngleSharp and AngleSharp.Css are their MIT
dependencies. Remove the unused sample greeting and CommunityToolkit dependency.
Versions and transitive hashes are committed in the NuGet locks.
Host the sanitized result in WinUI WebView2. Serve an intercepted logical HTTPS
document, with a random resource namespace for each open; no local HTTP listener.
The document response carries a deny-by-default CSP and no-referrer/no-store headers.
Document-authored styles, IDs and classes are removed. Application theme CSS is generated from validated six-digit colors, bounded finite sizes and escaped font names. Theme custom CSS escapes every less-than sign before HTML embedding, preventing style-element breakout; CSP and resource interception continue to deny remote resources.
Disable scripts, web messages, host objects, frames, downloads, popups and permission
requests. Reject all browser resources except the exact document and allowlisted
local image requests. External HTTP(S) navigation requires a real user gesture
and opens in Windows' external handler. User-initiated Markdown/text file links
open native reader windows after the same namespace/path validation and physical
folder check. The new reader retains that required physical root for every
manual/automatic reload; it cannot follow a later junction outside the original
grant. Other URI schemes and executable/non-document targets remain blocked.
Decode resource paths once; reject traversal, alternate streams, devices and Windows
filename aliases. Before reading an image, resolve its opened handle with
GetFinalPathNameByHandleW and compare it against the physical document directory.
Read from that same handle, preventing a check/reopen race. Remote images are blocked.
Bound documents to 16 Mi characters and individual images to 16 MiB.
## Options considered
| Option | Benefit | Cost |
| --- | --- | --- |
| Markdig, sanitization and WebView2 | Maintained parsing; compatible HTML/CSS surface | Requires explicit browser/filesystem containment |
| Parser plus unrestricted HTML | Small initial implementation | Exposes script, resource and native navigation risks |
| Entirely native text layout | Avoids an embedded browser | Reimplements tables, themes, print and navigation |
## Consequences
The reader supports a picker, literal launch arguments, manual reload and debounced save watching.
The 20 original themes are embedded unchanged, with their MIT notice shipped in THIRD-PARTY-NOTICES.md. System/SF Mono defaults map to Windows font fallbacks. Document theme selection persists separately from the AppKit shell theme and retains the viewport. Editable themes and native heading/section controls are implemented and verified; full syntax-color parity and new synchronized-source acceptance remain pending. Automatic save
reload uses fixed native ExecuteScriptAsync queries on the checked current document;
this API works while authored scripts remain disabled. Browser results must be exactly
two finite numeric fractions, clamped to 0–1; only invariant numeric arguments enter the
fixed restore expression. No strings from documents become executable script.
Any later document-created JavaScript or native message bridge requires a revised CSP,
origin/nonce/schema checks and tests before enabling it. Custom CSS must not introduce network or file access.
## Native source and find
The optional source surface is a native read-only TextBox holding the same bounded,
literal snapshot as the reader. It does not parse HTML. Core find uses case-insensitive,
nonoverlapping UTF-16 source positions and pairs counts with the rendered surface.
Use the pinned runtime's [native finder](https://learn.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2find.startasync), suppress its default dialog, and provide one native Ctrl+F toolbar.
Query strings enter the native API directly; no search term becomes executable script.
Disable browser feature accelerators so the host owns find/print/reload commands;
[standard movement, selection and copy shortcuts remain available](https://learn.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2settings.arebrowseracceleratorkeysenabled?view=webview2-dotnet-1.0.4191.47).
Typing is debounced and native searches serialized. Check the current page/navigation
before applying results; repeat the current term after reload/theme navigation. Search
reveals sections temporarily and restores their saved choices on close, incorporating
explicit native section commands during search. The literal source projection mirrors browser folds while preserving the canonical
snapshot and original query positions. Authored scripts, messages and host objects remain disabled.
Reading keys use the current app UI thread's existing message hook, bounded native
command enums and exact browser/read-only-source focus. Find/dialog controls retain
normal input. Translate only reading input with the active keyboard layout and
ToUnicode flag 4, preserving dead-key state; Alt/Windows/extra Ctrl modifiers are
ignored. Timed prefixes reset on focus/page/window transitions; held keys cannot
imitate gg/heading sequences. Dispatch rechecks focus. Browser scrolling and visible
heading operations use fixed host scripts and generated numeric identities only.
No content script, native bridge or dependency is added. Physical-key browser and
text-entry acceptance remains distinct from pure key-model tests.
## Parser-derived source provenance
The source synchronization foundation maps AST headings and raw HTML tokens to
original UTF-16 offsets. ATX/Setext, quote/list prefixes, fences, duplicate labels
and frontmatter retain parser identity rather than text/regex matching. Raw HTML
line lookup uses indexed offsets; source text remains immutable. Only headings
surviving the ordinary passive sanitizer receive a source identity.
A fresh per-render private attribute carries this provenance through sanitation;
its generated name is allowed only for the internal outline render, then removed
from every element before presentation. The public renderer never admits it, and
authored IDs/data attributes remain stripped. No source marker grants a browser
capability. These parser tests do not prove browser behavior. ADR status remains proposed.
## Literal source folding and scroll synchronization
A pure source projection normalizes native CR display offsets, retains heading
lines and replaces folded bodies with plain ellipses. Nested choices remain
independent; search expands the projection without changing the original snapshot.
Native selections map original UTF-16 query ranges into the visible literal text.
A serial 150 ms owned-window timer captures only finite browser scroll coordinates,
generated heading indices and collapsed bodies. Frames are bounded and validated,
with current document/navigation/query/snapshot guards. Native TextBox character
rectangles pair with actual generated browser heading tops for piecewise linear
scroll mapping. Feedback holds protect native find selections and prevent reverse
scroll oscillation. Hidden panes and closed windows do not synchronize.
The native coordinate conversion uses the owned TextBoxView's content space; its
scroll offset must not be added a second time. See the
[Microsoft implementation](https://github.com/microsoft/microsoft-ui-xaml/blob/main/dxaml/xcp/core/native/text/Controls/TextBoxBase.cpp).
Hosted tests must measure actual heading glyph/viewport positions, physical source
scrolling and fold/find/reload restoration before this behavior is called verified.
No authored script, message bridge, rich-text interpretation or dependency is added.
## Verification and follow-up
Core regression tests cover GFM, hostile HTML and resource paths at 100% core line
coverage. Native adapter tests cover BOM decoding, byte limits, exact image bytes and
a real junction pointing outside the document folder. Browser/UI acceptance is a
separate gate; these unit tests do not prove browser behavior or release signing.
- [ ] Review this decision and complete document UI acceptance.
- [x] Complete hosted save/replacement/deletion/scroll, editable-theme, outline/frontmatter and literal-source acceptance.
- [x] Complete paired find and physical vim acceptance in native, published and installed builds.
- [ ] Complete synchronized source and print acceptance from PRODUCT.md.
- [ ] Verify WebView2 runtime deployment and exact signed installed release artifacts.
## References
- [Markdig](https://github.com/xoofx/markdig)
- [HtmlSanitizer](https://github.com/mganss/HtmlSanitizer)
- [Microsoft WebView2 security guidance](https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/security)
- [Native WebView2 script execution](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2)
- [GetFinalPathNameByHandleW](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew)
@@ -0,0 +1,90 @@
# ADR-0003: Independent print styles and native Windows output
**Status:** Proposed for maintainer review; all5 native/published/installed print cases pass
**Date:** 2026-10-11
**Deciders:** James Maes, repository maintainer
## Context
Pinned markdownViewr `6d08bc9` temporarily replaces screen CSS and restores it in
its macOS modal print completion callback. Windows WebView2 `ShowPrintUI` returns
void and does not expose that completion lifecycle. Printing must retain the
reading theme, font zoom, scroll, find, source and nested collapse state after
success, cancellation or failure. Use published AppKit 0.1.0 and existing WebView2.
## Decision
Keep reading theme/custom CSS in a stylesheet with `media="screen"`. A deliberate
File/Print or Ctrl+P command generates a separate host-owned `media="print"`
stylesheet using validated saved print choices. Stylesheet media attributes also
contain custom CSS with unbalanced braces: print CSS cannot affect screen media.
Never replace reading CSS, mutate canonical content, expand DOM collapse flags,
stop find or navigate/reload merely to print. Print CSS shows generated collapsed
bodies; their original `hidden` attributes remain intact for the screen.
Provide Clean Printing (reference defaults), Active Theme, Plain HTML and named
built-in/user themes; backgrounds off/images on; standard/matching font zoom;
full/document width; optional borders and 0–96 pixels of page padding. Native
Windows print UI owns printer selection and cancellation. PDF saving uses an
explicit Windows App SDK destination picker and awaited WebView2 PDF output. PDF margins
are 0.5 inches, matching the reference's 36 points; scale is handled in font CSS.
No completion or successful printer job is inferred from opening native print UI.
Hosted PDF generation moved the same screen glyph41pixels after its awaited
operation; capture validated numeric reading geometry before PDF output and
restore its vertical position in finally, only for the same ready navigation.
Actual glyph/view rectangles verify restoration; the observed UIA scroll scalar
reported200% before printing and cannot serve as the viewport baseline.
Use the installed App SDK save picker with AppWindow.Id; do not introduce the
legacy WinRT picker/elevation constraint into the new PDF path.
Print/PDF uses the existing passive document, local image containment and resource
interception. No authored scripts/messages, remote resources, new native bridge,
output path from document content or private document/path logs. Fixed host script
assigns JSON-serialized CSS to a private stylesheet and validates the same ready
navigation afterwards. Pause native source feedback during the app's print draft/
PDF operation; geometry queries reject print-media frames while native print UI runs.
## Options considered
| Option | Benefit | Cost |
| --- | --- | --- |
| Separate media styles on the current passive page | Preserves screen state without a completion callback; existing image policy | Requires actual print-media/output and state tests |
| Replace screen styles, then restore them | Mirrors the macOS implementation | No reliable native print-UI completion signal; premature restoration changes output |
| A second print browser/window | Separate DOM snapshot | Duplicates page/resource lifetimes and navigation controls; extra browser state |
## Consequences and acceptance
Settings drafts are copied; cancel does not save them. No printer job is sent by
hosted acceptance: exercise native cancel and save owned fixture PDFs. Use Windows'
built-in PDF rasterizer to inspect real image/background/text output, with retained
PDF/PNG evidence. The test project targets the existing Windows SDK; no new PDF
library or runtime dependency is added. Keep 100% measured core coverage, full native/
published/stable/dev suites and the hostile-document controls. Original3 hosted native cases pass ec5f98f/38125751638, zero failures/skips:
actual default/themed PDFs and unchanged glyph/view/source/collapse state. Full
cleaned native/published/stable/dev gates and broader layout/font/padding/find
comparisons remain open. Retained PDF/PNG/TRX evidence is under
artifacts/print-exact-filename-38125751638.
Sources: [Microsoft printing overview](https://learn.microsoft.com/en-us/microsoft-edge/webview2/how-to/print?tabs=dotnetcsharp),
[App SDK save picker](https://learn.microsoft.com/en-us/windows/apps/develop/files/pickers-save-file),
[pinned print coordinator](https://github.com/darinkelkhoff/markdownViewr/blob/6d08bc9d27d7d7b2c2fa95bb282da330b8cf9021/ContentView.swift).
## Expanded output proof in progress
The native fixture now compares actual first-heading ink bands for Standard and
Match Screen font scaling and40pixel padding. Another fixture creates a saved
user theme with240pixel content width and compares actual block-edge pixels with
borders off/on and full-page width. Each PDF is retained before raster assertions.
Successful exports also verify paired query/count, actual native source selection
and original reading paint/geometry. These new cases compile/format but have not
run; full expanded native/published54 and installed53 per channel remain required.
Full hosted acceptance `bf4fcdc` / CI38128311242 passes54 native/published and53
installed stable/dev tests with zero failures/skips. All5 print cases pass in
every build. Actual PDF glyph ratios,40px padding,240px custom width/full-page
edges, optional borders and paired find/source selection pass;24 generated PDFs
are retained. UIA screen text metrics round to integer font boxes (35px to44px at
121%), so the PDF case verifies real increasing screen glyphs/completed zoom and
measures precise font ratios in printed ink. Original exact screen restoration
and PDF pixel assertions remain. Evidence: artifacts/print-font-verified-installed-38128311242.
Unsigned fixtures do not prove production signing or a successful physical printer job.
@@ -0,0 +1,59 @@
# ADR-0004: Native document windows and private recent files
**Status:** Proposed; implementation and core tests pass, hosted lifecycle acceptance pending
**Date:** 2026-10-11
**Deciders:** James Maes, repository maintainer
## Context
Pinned markdownViewr uses SwiftUI DocumentGroup for independent document windows,
file opening and recent documents. Its readable types include md/markdown/mdown/mkd
and plain text. Windows must preserve each reader's watcher, source/find/collapse
and font zoom when another document opens or closes. Use published AppKit0.1.0.
## Decision
Track strong references to native WinUI XAML MainWindow instances on the shared
UI thread; remove them on Closed. Each owns its browser, watcher, load scheduler,
keyboard hook, find/source/collapse and zoom. Pass the initial literal path to
that window rather than reading process arguments in every Loaded callback.
File/New window and Ctrl+N create a blank reader; Close window/Ctrl+W close only
that reader; File/Exit closes all. Close the shared activity log only when the
last document window closes. Show splash/startup update checks only once.
Use the current App SDK FileOpenPicker(AppWindow.Id) and PickMultipleFilesAsync,
including all five reference extensions. Reuse a blank window, focus an already
open canonical path within this process, or create a new document window.
Cancellation leaves readers intact; failed opens retain any existing reader.
Windows registration and cross-process activation now have local implementation
and isolated boundary evidence in ADR-0005. Explorer and installed lifecycle
acceptance remain required; literal process arguments alone are not association
proof.
Store at most20 successful absolute paths in the private AppKit settings file,
deduplicated with Windows path comparison. Update only after a parsed snapshot
commits. Clear history removes entries without deleting files or closing readers.
Keep paths out of activity logs/telemetry. Each preference save merges only fields
changed since that window's last successful save into the latest stored settings;
older window snapshots cannot resurrect cleared history or overwrite sibling edits.
## Options and consequences
Separate tracked XAML windows reuse existing document adapters and native lifetime
handling. Tabs would require additional detach/reattach state and do not supply the
reference's independent windows. Creating AppWindow directly does not attach XAML
content. Current duplication detection covers one process; OS activation and
stable/dev identity boundaries have local implementation/isolated tests in
ADR-0005; actual application and installed acceptance remains pending.
376 core tests pass with100% measured lines, including bounded/corrupt history,
case deduplication and sibling-safe settings merging. Require hosted real two-window
startup, actual independent glyph zoom, physical Ctrl+N/Ctrl+W, surviving save reload,
recent duplicate activation/missing errors, clear without stale resurrection and
Exit. Three new native cases are compiled; none has run on the local desktop.
## References
- [Microsoft: Multiple WinUI windows](https://learn.microsoft.com/en-us/windows/apps/develop/ui/multiple-windows)
- [Microsoft: App SDK multiple-file picker](https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.windows.storage.pickers.fileopenpicker.pickmultiplefilesasync)
- [Microsoft: Single-instance activation lifecycle](https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/applifecycle/applifecycle-single-instance)
@@ -0,0 +1,96 @@
# ADR-0005: Windows file registration and activation
**Status:** Implemented locally; isolated boundary tests pass; native/installed acceptance pending
**Date:** 2026-10-11
**Decider:** James Maes, repository maintainer
## Context
The Windows counterpart must open Markdown/plain-text files from Explorer and
subsequent launches without replacing existing readers. Stable, development,
portable previews and isolated test profiles must retain their identity boundaries.
Relative arguments must keep the launching process's working-directory meaning.
## Decision
Use Velopack install/update/uninstall fast callbacks to register only installed
copies. The registered executable is the installation's stable
`current/MarkdownViewr.Windows.exe`. Advertise `.md`, `.markdown`, `.mdown`, `.mkd`
and `.txt` through per-user ProgIDs, OpenWithProgids and Default Apps capabilities.
Quote the executable and literal `%1` argument. Set `AllowSilentDefaultTakeOver`
as REG_NONE; never write extension defaults, UserChoice or machine-wide keys.
A ProgID identifies channel and canonical executable path. Uninstall first
checks that its command remains unchanged, then removes only owned entries.
Another channel, another installation and deliberate user retargeting survive.
Builds and portable copies do not register file types.
Use Windows App SDK AppInstance to choose the primary for the executable and
profile directories. SDK rich file activation and ordinary command-line paths
become the same fixed native document request. Resolve relative paths before
crossing processes; the public SDK launch envelope cannot carry sender CWD.
Deliver requests through a current-user-only .NET named pipe. Accept only a
source-generated bounded schema: at most128 absolute paths, a1MiB frame and an
optional fixed invalid-path notification. Reject unknown fields; no command or
script operation exists. Connection/read/acknowledgement waits are bounded to
10seconds, with at most16 requests queued before XAML attaches its dispatcher.
Malformed/disconnected peers cannot stop later valid requests. App shutdown
cancels and disposes the listener. No dependencies or generic RPC layer are added.
The foreground launcher permits the primary to focus an existing document.
Dispatch all window creation on the native UI thread; duplicate paths include
pending initial loads and preserve watcher/source/find/zoom state. Empty launch
focuses an existing reader. An invalid startup path gets a fixed native message;
logs contain only failure kind/HRESULT.
## User-selected default file types
Settings → General exposes a native Choose default file types button. Read-only
checks require this installed executable's unchanged command and the exact owned
RegisteredApplications value. Portable/unregistered previews instead show install
or repair guidance and cannot launch the selection action. Nothing selects or
reclaims a default, and `.txt` is explicitly optional.
Windows 11 23H2+ uses `ms-settings:defaultapps?registeredAppUser=` plus the
URI-escaped registered value name (the installation/channel ProgID, not a display
name). Earlier builds use general Default Apps with instructions to search `.md`;
this conservatively includes Windows 11 builds predating the supported deep link.
The native action revalidates ownership at click time, handles launch failure with
manual OS guidance and logs only error kind/HRESULT.
General also reports the effective `.md` default using Windows' read-only
[AssocQueryStringW](https://learn.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-assocquerystringw)
with [ASSOCSTR_PROGID](https://learn.microsoft.com/en-us/windows/win32/api/shlwapi/ne-shlwapi-assocstr).
Compare the returned ProgID with this installation/channel and require unchanged
registration ownership. An unavailable query stays Unknown; registration alone
never means Default. The Unicode buffer is capped at1024 characters, input is
fixed `.md`, and no UserChoice/hash/default writes or competing-app paths are
read or displayed. Refresh when returning from Windows Settings or explicitly
through Refresh status. Seven real read-only/isolated adapter cases pass;
actual user-selected default/Explorer acceptance is still required. Document content has no route
to invoke it. Actual Windows chooser/selection and Explorer acceptance are pending.
## Evidence and remaining acceptance
Eleven actual isolated registry/pipe/path tests pass, including two Default Apps
routing/ownership cases retained in `artifacts/default-files-boundaries/`. They cover command/icon
quoting, all five extensions/capabilities, REG_NONE marker, preserved defaults,
channel/installation cleanup, retarget protection, sender-relative Unicode,
profile identities, startup queuing, acknowledgements, malformed recovery and
idempotent cancellation/rebinding. Red/fixed TRXs are retained under
`artifacts/activation-boundaries/`. These do not prove Explorer or XAML behavior.
Two real secondary-app/profile/duplicate/watcher cases compile but have not run.
Require controlled native/published/installed acceptance, actual install/update/
uninstall readbacks and Explorer Open With before closing issue #14. Production
signatures and release publication remain separate requirements.
## References
- [Microsoft: App SDK single-instance lifecycle](https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/applifecycle/applifecycle-single-instance)
- [Microsoft: Rich activation](https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/applifecycle/applifecycle-rich-activation)
- [Microsoft: Launch Default Apps settings](https://learn.microsoft.com/en-us/windows/apps/develop/launch/launch-default-apps-settings)
- [Microsoft: File association ProgIDs](https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids)
- [Microsoft SDK activation implementation](https://github.com/microsoft/WindowsAppSDK/blob/main/dev/AppLifecycle/AppInstance.cpp)
- [Velopack application hooks](https://docs.velopack.io/reference/csharp/Velopack.VelopackApp)
+210
View File
@@ -0,0 +1,210 @@
# MarkdownViewr signing administrator actions
Verified on2026-10-12 using authenticated Azure CLI and ARM resource readback.
The existing account, validation and profile were reused. A separate secretless
MarkdownViewr CI identity, exact release-environment trust and profile-scoped
signer assignment are created and independently read back. All seven protected
GitHub release variables match the verified deployment. gclo's identity,
credentials and assignments were preserved.
## Verified deployment and local signing proof
| Setting | Verified value |
| --- | --- |
| Subscription | `8ef2479e-3346-4cba-93ad-101c6cd237a0` (`Kof22 Signing Account`) |
| Tenant | `4ebeb0ad-5782-487a-a1b4-5080255774cf` |
| Account / region | `kof22signing` / `eastus` |
| Regional endpoint | `https://eus.codesigning.azure.net/` |
| Profile | `releases`, Active, PublicTrust |
| Certificate subject | `CN=James Maes, O=James Maes, L=Chester, S=il, C=US` |
| CI client ID | `8f51ec7a-5d91-45db-a374-c628566fc383` |
| Application object ID | `1640971f-8e5c-4f3a-802e-8633630e544c` |
| Service-principal object ID | `4e9e7d2b-5df4-4312-9d01-9845922c4d92` |
The sole CI federation uses the exact immutable subject shown below. Its signer
role is limited to the `releases` certificate profile; no Owner/Contributor role
or client secret was added. The protected GitHub environment permits only `v*`
tags. No tenant security controls changed.
A local representative test signed disposable copies of all six owned binaries
from `321816c` with Microsoft.ArtifactSigning.Client `1.0.146`, SHA256 and RFC3161
SHA256 timestamping. The client NuGet package's Microsoft author and NuGet
repository signatures verify. Each binary passes `signtool verify /pa /all /v`
with zero warnings/errors, the exact certificate subject above, and Microsoft
Public RSA Time Stamping Authority. The existing strict signature helper also
passes all six files, including its SHA256 digest check. Logs, original/final
hashes and signature receipts are retained locally in
`artifacts/signing-proof-321816c/`; running owner previews were untouched.
This test uses the authorized interactive administrator's existing signer role.
It does not prove the new CI identity's OIDC exchange, installer signing or final
shipped artifacts. Complete standards PR#31 review/publication, adopt the
published catalog builder, and run the protected release integration before
claiming production readiness. No stable 1.0 or dev release was published.
## Interactive sign-in when device flow is blocked
The owner-reported Azure CLI error530035 means Security Defaults blocked
access. [Microsoft's current guidance](https://learn.microsoft.com/en-us/entra/fundamentals/security-defaults#block-device-code-flow)
explicitly states that Security Defaults block device-code authentication. Keep
those protections enabled. Use normal interactive Windows authentication rather
than repeating `--use-device-code`:
```powershell
az login --tenant koftwentytwooutlook.onmicrosoft.com --output none
```
Complete account selection and MFA personally. Current Azure CLI uses Windows
[Web Account Manager](https://learn.microsoft.com/en-us/cli/azure/authenticate-azure-cli-interactively#sign-in-with-web-account-manager-wam-on-windows)
by default. If interactive sign-in also fails, inspect that sign-in's actual
error and Entra sign-in log; do not assume device enrollment or broader resource
roles will resolve it. Authentication alone still does not establish access to
the signing account/profile or grant CI signing permission.
If Windows account selection cannot use the administrator's passkey provider,
use Microsoft's supported browser flow with a process-only environment setting:
```powershell
$env:AZURE_CORE_ENABLE_BROKER_ON_WINDOWS = 'false'
az login --tenant koftwentytwooutlook.onmicrosoft.com --output none
```
Run this in a dedicated PowerShell process; it does not persist CLI settings or
change tenant policy. Use the owner's browser with the enabled/unlocked
[1Password extension](https://support.1password.com/save-use-passkeys/) for its
stored passkey. Complete MFA personally. Do not export passkeys or repeat the
blocked device-code flow. If browser authentication also fails, inspect its new
error rather than assuming the passkey or tenant registration is at fault.
## Verify the deployed resource
Public Microsoft OpenID discovery on2026-10-11 resolves the supplied directory
`koftwentytwooutlook.onmicrosoft.com` to tenant GUID
`4ebeb0ad-5782-487a-a1b4-5080255774cf`. Its
[discovery metadata](https://login.microsoftonline.com/koftwentytwooutlook.onmicrosoft.com/v2.0/.well-known/openid-configuration)
returns issuer `https://login.microsoftonline.com/4ebeb0ad-5782-487a-a1b4-5080255774cf/v2.0`.
This identifies the directory only; it does not establish the signing subscription's
tenant, regional endpoint, active profile or verified publisher subject. The
authenticated ARM readback above now confirms this directory is the signing
subscription's actual tenant. The commands below remain the repeatable inspection
procedure; recheck them if the deployed configuration changes.
With an authorized interactive Azure session, inspect the actual tenant, regional
account URI and profiles. The account/subscription below are owner-provided setup
values; the returned resource must confirm them before configuration.
```powershell
$subscription = '8ef2479e-3346-4cba-93ad-101c6cd237a0'
az account set --subscription $subscription
$account = az account show --query '{subscription:id,tenant:tenantId,name:name}' -o json | ConvertFrom-Json
$resourceId = "/subscriptions/$subscription/resourceGroups/rg-code-signing/providers/Microsoft.CodeSigning/codeSigningAccounts/kof22signing"
$resource = az resource show --ids $resourceId --api-version 2025-10-13 -o json | ConvertFrom-Json
$profiles = az rest --method get --url "https://management.azure.com$resourceId/certificateProfiles?api-version=2025-10-13" -o json | ConvertFrom-Json
$resource | Select-Object name,location,@{Name='Endpoint';Expression={$_.properties.accountUri}}
$profiles.value | Select-Object name,id,@{Name='Type';Expression={$_.properties.profileType}},@{Name='Status';Expression={$_.properties.status}}
```
Check every command's exit status. Confirm the tenant GUID from `account.tenant`,
not the directory domain or subscription GUID. Select the actual Active PublicTrust
profile; `releases` is expected, not assumed. Read its issued certificate's full
`properties.certificates[].subjectName` and compare it with the profile's verified
publisher in the portal. If the API has no issued certificate, inspect the profile
and associated completed identity validation in the portal; do not invent a
subject. Use `resource.properties.accountUri` as the signing endpoint. gclo's
configured publisher name is reference evidence only; the new builder requires
the full X.500 subject rather than just the publisher's display name.
[Microsoft's account/profile schemas](https://github.com/Azure/azure-rest-api-specs/tree/main/specification/codesigning/resource-manager/Microsoft.CodeSigning/CodeSigning/stable/2025-10-13)
define these inspection fields.
## Existing gclo signing reference
Read-only release inspection on2026-10-11 confirms that
[gclo v1.1.0](https://github.com/KofTwentyTwo/gclo/releases/tag/v1.1.0)
ships a valid timestamped CLI signature. The archive matches its published
SHA256SUMS (`46f02d89c9d8a459e5b02598f9791fa7aab5c16b09c254bc997f8f235a6a49e2`).
Microsoft SignTool `verify /pa /all /v` on the extracted `gclo.exe` succeeds with
one verified signature, zero warnings/errors. Its exact certificate subject is
`CN=James Maes, O=James Maes, L=Chester, S=il, C=US`; the timestamp chain is
Microsoft Public RSA Time Stamping Authority. Local reference evidence is retained
in `artifacts/gclo-signing-reference/verify.log`; the application was not executed.
This confirms the existing release recipe and shipped publisher, not the current
Azure account/profile mapping or MarkdownViewr CI authorization. Recheck the
actual deployed profile subject before using it here. gclo's client identity has
its own repository trust; do not copy that client ID without an approved reuse
and a separate exact MarkdownViewr federated credential/signer assignment.
## Configure the CI identity and trust
Inspect existing CI applications and their owners first. Reuse an approved suitable
identity; otherwise create a repository-specific single-tenant Entra application
and service principal named `MarkdownViewr.Windows release signing`. No client
secret is needed. Record its application/client GUID and service-principal object
GUID separately. Preserve all existing credentials.
The repository OIDC customization readback on2026-10-11 is `use_default=true`,
`use_immutable_subject=true`, with prefix
`repo:KofTwentyTwo@47367449/MarkdownViewr.Windows@1413777106`. Recheck it before
creating trust:
```powershell
gh api repos/KofTwentyTwo/MarkdownViewr.Windows/actions/oidc/customization/sub
```
In Entra → App registrations → the selected CI app → Certificates & secrets →
Federated credentials, add the following exact credential after confirming the
current prefix. Do not copy gclo's credential or broaden trust to pull requests.
```json
{
"name": "markdownviewr-release",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "repo:KofTwentyTwo@47367449/MarkdownViewr.Windows@1413777106:environment:release",
"audiences": ["api://AzureADTokenExchange"]
}
```
For CLI administration, save that JSON locally, then use
`az ad app federated-credential create --id <application-object-id> --parameters <file>`.
Inspect existing credentials before creating; retain any unrelated entry and do
not overwrite a same-name credential whose fields differ.
[Microsoft's immutable-subject guidance](https://learn.microsoft.com/en-us/entra/workload-id/workload-identities-github-immutable-subjects)
explains the repository/owner ID components.
Assign only the signing permission at the verified profile scope:
```powershell
az role assignment create --assignee-object-id <service-principal-object-id> --assignee-principal-type ServicePrincipal --role 'Artifact Signing Certificate Profile Signer' --scope <verified-profile-resource-id>
```
Read back the assignment. Owner/Contributor permissions do not provide signing
rights. [Microsoft's role guidance](https://learn.microsoft.com/en-us/azure/artifact-signing/tutorial-assign-roles)
supports profile-scoped signing access.
## Configure and prove release signing
The protected GitHub `release` environment permits intended `v*` tags. Set the
verified values through `gh variable set NAME --repo KofTwentyTwo/MarkdownViewr.Windows
--env release --body VALUE` for `AZURE_TENANT_ID`, `AZURE_CLIENT_ID`,
`AZURE_SUBSCRIPTION_ID`, `ARTIFACT_SIGNING_ENDPOINT`, `ARTIFACT_SIGNING_ACCOUNT`,
`ARTIFACT_SIGNING_PROFILE` and `ARTIFACT_SIGNING_SUBJECT` (full subject).
The release caller now uses the reviewed standards v1.1.0 catalog builder;
PRs #31, #39 and #41 provide its implementation and verified CI corrections.
Current verified official pins are azure/login@v3.1.0 commit
`a641126d1b8aa4d1fa005f4f92df94a3a4c4c906` and
azure/artifact-signing-action@v2.0.0 commit
`c7ab2a863ab5f9a846ddb8265964877ef296ee82`. Its Windows release job uses
`id-token: write` and Azure CLI-backed credentials, SHA256 and RFC3161 timestamping.
[The official signing README](https://github.com/Azure/artifact-signing-action)
describes this OIDC integration.
Follow [release-signing.md](release-signing.md) for the owned-file catalog and
local SignTool requirements. A real representative signing run must verify the
exact shipped executables, managed binaries and installers with
`signtool verify /pa /all /v`, checking full publisher, trust and timestamp.
Generate final checksums/feed metadata after signing and fail publication on any
required signing/verification error. Active profile/configuration alone is not
proof. No account recreation, new identity validation or private-key export is
part of these actions.
@@ -0,0 +1,638 @@
# Historical Reader and Source Session Notes
Updated: 2026-10-11
## Active work and authority
Build the full Windows counterpart to Darin's markdownViewr using published AppKit
0.1.0. Approved identity: MarkdownViewr / MarkdownViewr.Windows / markdownviewr-windows.
Topic feat/windows-viewer, draft PR #3; PRODUCT.md remains the complete scope.
Owner authorized signed/DCO commits, pushes, repository configuration and ticket updates.
No merges, tags/releases or new Azure attempts. Human review acknowledgement remains
required. Local desktop reservation is unanswered; hosted runner UI tests are authorized.
## Confirmed WinUI clicked-content identity fix — 2026-10-11
Signed dcddefb pushed. Focused source 38121148429/job 114415967219 fails
at the added real browser assertion after repeat activation. The trace proves
ItemClick dispatches TextBlock twice (initial and repeated clicks), refreshing=False,
index=-1. Owned physical pointer is over the expected OutlineHeading:2. No reader
geometry change follows the repeated click. Thus content/container mismatch,
not source feedback or missing native event, explains the repeat-navigation bug.
Evidence: artifacts/source-activation-boundary-38121148429.log.
Attach the existing numeric DocumentHeading tag to the native TextBlock as well
as its ListViewItem; ItemClick accepts the tagged FrameworkElement. No title
lookup, arbitrary source command, extra state or changed scroll mapping.
Keep keyboard SelectionChanged and the actual browser/source/wheel regression.
Retain opted-in trace until original/full acceptance, then remove temporary probes.
The focused original red case is retained. Supersede remaining dcddefb diagnostic
CI with this confirmed fix; cancelled predecessor jobs are not acceptance proof.
Require complete fixed-head native/published/stable/dev results before cleanup.
## Activation boundary trace after physical reproduction — 2026-10-11
016c745 focused source 38120650945/job 114414500506 still fails at the
repeated last-heading alignment; owned physical down/up events appear in logs.
This falsifies the explanation that only UIA invocation bypasses ItemClick.
Do not change app logic again without observing the boundary. Add a trace only
under the existing fixture input-trace opt-in: clicked native type, numeric
heading identity and refresh guard. Record the owned pointer target and assert
the actual browser last-heading glyph before the unchanged native source check.
This separates missing activation, unexpected payload and reverse source feedback.
No authored-content/path logs, delays or weakened assertions. Current full CI
38120651378 remains observed until terminal before pushing another diagnostic.
## Physical repeat-activation diagnostic — 2026-10-11
Signed 2bf1d14 pushed. Focused source 38120152677/job 114413026121 still
fails at the repeated last-heading invocation (line 461), after both actual
middle-heading viewports pass. Native job 114413027224 also fails; build,
coverage, format, scripts, security and PR gates pass. No claim of full acceptance.
The shared ClickElement prefers UIA Invoke, whereas the new ListView behavior
handles ItemClick. Ranked explanations: automation does not dispatch ItemClick;
source feedback overwrites the jump; or unexpected clicked-item payload.
Change one input: use the existing owned physical mouse helper for the repeated
heading activation. Retain the initial UIA selection and all original source,
browser and final physical-wheel assertions. No additional app change or delay.
If physical activation succeeds, this distinguishes the activation path from
source feedback; if it fails, retain the trace before another implementation change.
Evidence: artifacts/source-reactivation-38120152677.log. Full suites remain required.
## Repeated outline activation after actual bidirectional source proof — 2026-10-11
Signed 22a3393 pushed. Source 38119623871/job 114411478222 advances past both actual
middle-heading source/reader assertions. Bounded physical wheel input works and
native source 2656.933 maps back to its reader heading. The failure is now line 461:
clicking OutlineHeading:2 again does not jump back, since it is already selected
and SelectionChanged does not fire. Keep the same original regression and bounds.
Evidence: source-bounded-wheel-diagnostic-38119623871.log.
The native ListView now enables ItemClick and repeats the same bounded numeric
heading command on item activation. Existing SelectionChanged remains for keyboard
selection navigation; repeated commands on a newly selected item are idempotent.
No debounce/prefix/new state or renderer capability. The original source test
already reproduces the repeat-activation bug before this fix; do not weaken it.
22a3393 CI 38119624417 / native 114411480085 / installed 114411479783 and other
producers remain observed until terminal before push; all full suites stay required.
## Bounded native wheel steps after measured per-event cap — 2026-10-11
Signed 05c4bc9 pushed. Source 38119175090/job 114410164596 reproduced a real input
failure: one upward detent moves source 5310.933→5223.933 (87px) and reader 6264→6164.
The subsequent 31.8-detent event moves only 580px, to 4643.933, leaving the middle
heading offscreen. Native source-to-reader movement is now observed; the driver
requested one oversized event. Retain source-wheel-diagnostic-38119175090.log.
The single change sends at most 4 detents per event, measures settled real native
offsets after each and bounds the approach to 12 events. Actual glyph correction
and all original source/reader/wheel assertions remain unchanged. No app-scroll
change. Current 05c4bc9 CI 38119175562/native 114410166891/installed 114410166667 and
other producers remain observed until terminal before the next push.
## Physical source-wheel driver after actual range diagnostics — 2026-10-11
Signed 06a1213 pushed. Source 38118669938/job 114408682304 still fails: actual range
min 0/max 7370, read-only False, value 5310.933 before; requested 2455.883 and after 2455.883.
The middle glyph remains unavailable and no resulting source geometry frame appears.
The UIA range accepts its scalar value but does not prove that the text viewport moved.
Retain source-range-diagnostic-38118669938.log and original full glyph assertions.
The single driver change now uses physical wheel input over the already verified owned
source target. One upward detent measures actual native movement; proportional wheel
input reaches the approximate text region, then actual glyph bounds adjust to 12px.
No UIA scalar setter or application mirror positions the viewport. Every source/reader
heading and final physical-wheel assertion remains. This also exercises the original
user gesture directly. Keep temporary diagnostics until original/full acceptance passes.
0f13849 published TRX confirms 48/49, zero skips, including actual zoom glyph/key/toolbar/
reload and editor-list cases. All other checks pass; installed channels not reached.
Current 06a1213 full CI 38118670343 (native 114408683591, installed 114408683491, core
114408684077, format 114408683974) remains observed until terminal before next push.
## Native zoom acceptance and source range probe — 2026-10-11
Signed/DCO 0f13849 is pushed; CI 38118157164 native 114407185408 finished FAILURE:
48/49, one failure/no skips. The actual zoom glyph/menu/toolbar/physical Ctrl+plus,
minus/reset and reload case passes; both new editor tests pass. Source focus retains
its heading, but the native scrollbar request does not reveal the middle heading.
Isolated source 38118156844/job 114407184273 reproduces the same timeout. Retain logs
at artifacts/zoom-scrollbar-full-ui-38118157164.log and source-scrollbar-diagnostic-
38118156844.log. Build/core/format pass; installed 114407185287 remains observed until
terminal before follow-up push. Current checkpoint and PR/issues reflect 0f13849.
The single diagnostic change captures actual native scrollbar min/max/value/read-only,
requested value and the resulting owned source geometry on timeout. Distinguish
unsupported native range movement, inaccessible glyph geometry and a bad initial
index-to-range estimate before replacing the driver. No app change or relaxed
alignment assertion. Core 330/100%, zero-warning build, format and docs remain required.
## Zoom implementation and real native source scrolling — 2026-10-11
1c4d1ad / CI 38117431818 is terminal FAILURE: native/published 47/48, zero skips;
installed channels not reached. Original post-focus last-heading alignment now
passes. The test reaches line461 and fails after UIA text-range ScrollIntoView:
no native ViewChanged/offset movement, with offscreen glyph bounds unavailable.
Evidence: artifacts/source-caret-diagnostic-38117431818.log and published TRX in
artifacts/source-caret-failed-installed-38117431818. This confirms caret positioning
fixed the observed reset; it does not yet prove full synchronization. The regression
now drives the source's actual native ScrollBar RangeValue, adjusts from actual
visible glyph bounds and retains all middle/last reader/source/wheel assertions.
A new standalone source-diagnostics workflow keeps this temporary feedback job
separate from required CI. The required-job invariant correctly rejected adding
an optional diagnostic job to ci.yml; its test and all production check names stay
unchanged. Remove the temporary workflow/tagged logs after the original case passes.
Per-window reference font zoom is implemented with native menu/compact toolbar,
rounded reset percentage and Ctrl+plus/equal/minus/0 reading commands. Scale starts
at 100%, uses 1.1 steps and 30–500% bounds; body/all headings/code font sizes scale
with invariant values while line-height/theme JSON remain unchanged. Reload/theme
navigation retains scale and existing viewport/collapse/find replay. Reference
ThemeManager scales fonts, not the whole browser; no unsupported WinUI ZoomFactor.
330 core tests and 100% measured lines pass at artifacts/reader-zoom-core. Zero-warning
build, format, six-document lint, two-pass script analysis and all 55 required Pester
boundary tests pass (artifacts/zoom-script-boundaries.xml). New hosted glyph/key/reload test added: 49 native/published and 48 per
installed channel expected; actual zoom acceptance is pending. No local GUI run.
Full PRODUCT remains active, including print #12, remaining zoom #13 acceptance,
source/raw-heading audit, windows/recents/activation, full theme/render/link parity,
standards inventory and actual signed final artifacts. Azure remains deferred.
## Source caret alignment probe — 2026-10-11
82898bd / CI 38117005710 isolated source job 114403773700 reproduced FAILURE
in 18 seconds. Original timeout is now retained: source focus before=5310.933,
last=5310.933; after physical pointer press, actual native offset resets to 0.
The provider cannot retrieve the offscreen last-heading glyph. This rejects the
hypothesis that GettingFocus captured an already-reset offset; deferred restoration
ran too early to prevent later native caret work. Original full assertions stay intact.
The single-variable probe positions a zero-length caret at the first visible native
character before focus, using bounded binary search over real native character
rectangles. Active find/nonempty selections remain independent. The previous snapshot-
guarded viewport restoration remains. DEBUG-src-focus records the native caret index;
remove probes/temporary CI job after actual original acceptance proves the fix.
Full/native/installed producers on 82898bd remain observed until terminal before push.
The enlarged 48-case suite's measured native failure duration is 178 seconds and
published acceptance exceeded 180 seconds before producing TRX. Full published/
installed UI suites receive a bounded 300 seconds (all cases/assertions unchanged).
The process helper retains stdout/stderr after timed-out child termination before
throwing; a 10-second termination bound still fails closed. Other command timeouts
remain unchanged. A headless owned-child timeout probe checks retained evidence.
## Tight native source diagnostics after editor acceptance
f650e36 / CI 38116258625 native job 114401630861 finished FAILURE:
47/48, one failure/no skips. Both new actual editor-menu/probe/order/cancel/removal
tests passed; source focus preservation still fails at the post-focus last-heading
glyph assertion. The timeout's extra rectangle read throws E_UNEXPECTED before
printing logs, masking the original focus data. Retain native log at
artifacts/editor-focus-ui-38116258625.log. Installed job 114401630683 finished
FAILURE at its 180-second dotnet timeout, with no completed TRX or installed-channel
proof. Retain logs/screenshots at artifacts/editor-focus-failed-installed-38116258625
and artifacts/editor-focus-installed-38116258625.log. All producers are terminal;
build/core/format, CodeQL, scripts, security and PR gates passed.
The diagnostic fix prints owned logs/tree first, catches only failed diagnostic
rectangle retrieval and rethrows the original timeout; no test outcome is relaxed.
A temporary read-only Windows source-diagnostics job runs only the same exact native
viewport case (15 seconds after build) and retains TRX. All required 48-test native/
published and 47-test installed suites remain unchanged. No app-scroll change in
this diagnostic increment; gather GettingFocus before/last offsets and actual glyph
bounds before another hypothesis. Remove DEBUG-src-focus probes/job when resolved.
The diagnosing-bugs workflow is applied; hosted setup dominates wall time because
local GUI input is unavailable. Core loop remains 312 tests/100% measured lines.
Reference backlog now has print issue #12 (native theme/background/image/zoom/width/
border/padding, success/cancel/error restoration and actual output) and zoom #13
(1.1 steps, 30–500%, Windows Ctrl+plus/minus/0, actual scaling/window state). Full
PRODUCT scope remains active. No local GUI/Azure/merge/tag/release or false review.
## Current named-editor and source-focus increment — 2026-10-11
2dc509d / CI 38115411266 is terminal FAILURE: native/published 45/46,
one failure/no skips; installed channels not reached. Build/core/format,
CodeQL 38115411093, scripts 38115410579, security 38115411111 and PR gates
38115411139 passed. Logs and published TRX retained at
artifacts/source-sync-focus-failed-installed-38115411266 and source-sync-focus
native/installed logs. New glyph assertion proves the UIA range request does not
align native source. Numeric logs show entering SourceText resets offset to 0
from target 5310.9 before pointer press. The source now restores its pre-focus
viewport asynchronously with snapshot/closed/find guards. The test also verifies
last-heading alignment after physical focus before requesting middle alignment;
actual glyph bounds are retained on failure. No acceptance weakening or GUI input.
Named editor lists now persist stable IDs/order, bounded names/.exe paths and
per-editor file/folder choices. Legacy paths migrate once; the compatible path
mirrors the first choice and clears on last removal. The native manager edits a
copied list with add/remove/move/save/cancel; one editor is direct, several use a
saved-order submenu. Each launch revalidates the selected identity and receives
one literal file or parent-folder argument. No dependencies or shell interpolation.
312 core tests/100% measured lines pass at artifacts/editor-catalog-core,
including stable migration, corrupt/duplicate/capped choices, trimmed AppKit
save/load/removal and literal Unicode/punctuation/UNC/root folder arguments.
Two new hosted editor tests await proof: actual selected probe file/folder arguments,
persisted reordering, cancel and final removal/native disabled command. Expected
48 native/published and 47 per installed channel. Build zero warnings passes;
final format/docs/hook gates and signed commit/push are next.
Full PRODUCT.md remains active; no local GUI/Azure/merge/tag/release or fabricated
human acknowledgement. Published AppKit 0.1.0 retained. Source raw-heading audit,
new acceptance, print, windows/recents/activation, broader theme/rendering/link
parity, standards inventory and actual signed stable/dev final bytes remain.
## Current source synchronization increment — 2026-10-11
Previous pushed b0f3b8638f16d75e4dce5dc6f2cb585fb5214927 is fully verified:
CI 38113042618, CodeQL 38113042570, scripts 38113041887, security 38113042545 and
PR gates 38113042454 SUCCESS. Native job 114392386224 and installed job
114392386003 succeeded; retained TRX proves published 44/44 and stable/dev 43/43,
zero failures/skips, at artifacts/source-anchors-verified-installed-38113042618.
Fixtures remain synthetic/unsigned; all prior producers are terminal.
The current increment implements literal nested source folding and bidirectional
heading-anchor scroll mapping. Canonical snapshots remain immutable; native CR
and folded offsets retain exact find selections. Browser geometry is bounded,
validated and current-page/query guarded; feedback holds preserve explicit find
viewports. Microsoft TextBoxView content coordinates do not receive a duplicate
scroll offset. No new dependency or authored scripts/messages/host objects.
300 core tests/100% measured lines pass at artifacts/source-sync-core. Two new
hosted tests assert actual native/browser heading bounds, physical source scrolling
and nested fold/find/reload restoration: 46 native/published and 45 per installed
channel expected, not yet verified. No local desktop input occurred.
Next: commit/push this reviewed increment, observe its exact producer runs, retain
actual TRX/logs and address failures. Keep #10 open until acceptance and raw-heading
reference audit pass. Full PRODUCT scope remains: broader editor/theme/rendering/
link parity, printing, windows/recents/activation, standards inventory/current-head
human review and exact signed stable/dev artifacts. Published AppKit 0.1.0 remains;
no Azure attempt, merge/tag/release or fabricated human acknowledgement.
## Verified reader and editable themes
77dd04da96b75938a1c1d38c8bebe4851c11a011 CI 38102249889 SUCCESS: build/test,
format, native/UI 26/26, published 26/26, installed stable/dev 25/25 each, no skips.
Four fixture byte gates verified 536 files in both payloads; AppKit probes applied and
restarted within both channels. Fixtures are synthetic and unsigned, not production
signing evidence. CodeQL/security passed. Artifacts: verified-reader-38102249889.
Reader provides picker/Ctrl+O/literal launch paths, sanitized GFM, bounded passive
local images, debounced saves/replacement/deletion recovery and relative scroll.
Native status declaration before WebView2 solved embedded-provider traversal; exact
missing/recovery assertions retained. Initial window fits the monitor's work area.
20 original JSON themes retain Darin's MIT notice. Selection persists separately from
AppKit shell themes; actual dark/light paint checked. 30ca0943c09a8662794c2d400f1b5c49de426942
CI 38102867556 fully SUCCESS, including 29 native/UI tests, editable colors/code font/CSS,
validation/restoration and both installed channels. CodeQL/scripts/security passed.
Later size-17 edit also passed among 29/30 UI tests. Theme import/export/delete and
additional font preferences still need a full reference-parity audit.
## Frontmatter and outstanding diagnostic
3b6ebf83d0d88729887ce7678b0e3bf5cbe85989 implements hidden default, escaped line-based
metadata and sanitized Markdown modes; saved native choices, CRLF/delimiter fidelity.
149 core tests/100% coverage. CI 38103609174 failed accessible-table lookup, 29/30 UI;
CodeQL/security/scripts passed. a141929820694976ab5ab13b2a9a86e687284bf4 adds semantic
caption and row headers. CI 38104015508 now passes table presence but fails table
removal after Markdown selection, still 29/30. Installed producer finished FAILURE at the same
mode-switch assertion; CodeQL 38104015459/scripts/security passed. No run restarted.
New diagnostic keeps the exact assertion and records fixture settings/log/UIA tree.
Issue #5 mitigation is verified: scripts 38104585530 captured InstalledUpdates.Tests.ps1 /
CommandInfo.get_Parameters -> UseCorrectCasing.AnalyzeScript, matching upstream #1708.
Signed ce9afc62378a1275dc723ee444066bd2c732cef5 isolates casing, then checks all other
unchanged rules. Both negative fixtures, 13-file baseline and bounded 20-case probe
pass locally. Actual 443b492 scripts 38104988748 SUCCESS: all 13 files and 55 Pester
tests, no failures/skips; JavaScript passed. No suppression, retry or version change.
PR #3 can close #5 on reviewed merge; upstream itself remains upstream.
## Outline milestone being prepared
Pushed outline head: 5c3988e012b46aff2959eabd16c6de2b7381ab04. Authoritative CI
38104585881 is terminal FAILURE: 31/33 native/UI pass, including actual heading jumps,
independent nested collapse and retained theme/reload state. Depth choices pass; physical
resize fails. Frontmatter still fails because Markdown click never dispatched: settings
remain Metadata, only one reload logged, submenu remains open. CodeQL 38104585849,
security and PR gates passed; scripts 38104585530 captured the race above.
Follow-up adds an explicit Thumb hit-test template. Physical mouse helper moves directly
to the requested control, verifies that exact target or its descendant, then clicks;
an animated path across parent menu rows can close a submenu before the click. Depth
popup is explicitly collapsed before real divider drag and hit identity checked.
Settings/table/resize assertions remain; no local GUI input. Hosted verification needed.
Issue #9: sanitized heading labels, GFM fragment mapping, generated numeric identities,
stack-free section construction, duplicate/nested headings and passive markup.
Native persistent sidebar/depth/drag width and section operations compile. Fixed host
scripts retain independent nested collapse across matching-heading reload/theme changes.
161 core tests pass with 100% line coverage; zero-warning build. Native resize/jump/
collapse/state tests added, not run locally. Observe the next exact hosted handles.
## Latest input acceptance
443b4928eed46641c759012e741a27723e943826 is pushed. CodeQL 38104989139,
security 38104989109, scripts 38104988748 and PR gates 38104989102 SUCCESS;
CI 38104989141 build/format pass, native/UI 31/33. Exact-target pointer verification
passes but Markdown choice stays Metadata, and physical resize stays unchanged.
Installed producer is still being observed; no cancel/restart. Captured UI log:
artifacts/pointer-ui-38104989141.log. Fixture screenshot from the prior run shows
Markdown hover while Metadata remains checked, consistent with input not dispatched.
New follow-up holds mouse-down until the native UI processes it before mouse-up;
real divider drag separately settles start/down/move/up. Outcome assertions and
10-second limits unchanged. Resize failures now capture saved width/rectangle/tree.
Zero-warning build and format pass; no local input. Push after existing producers end,
then observe its exact new handles. Full application scope remains active.
## Next and remaining scope
Finish frontmatter mode-switch diagnosis and hosted outline acceptance; update #4/#6
scoped acceptance and #1/#5/#7/#8/#9 accurately. Continue source pane/synchronization,
find/vim keys, external editor, print state/options, multiple windows/recents and
Windows file associations. Audit syntax-highlighting/local-link/theme parity, standards
inventory and reviewed AppKit API adoption. Verify real signed stable/dev final bytes
before production publication; active Azure profiles do not prove successful signing.
Pinned reference: 6d08bc9d27d7d7b2c2fa95bb282da330b8cf9021 under
AppKit/artifacts/markdownviewr-reference. Preserve original MIT attribution on adaptations.
## Literal source and precise native event follow-up
Latest prior head d59dba2c96d126730f491720fd548c43c30dd97b: CI 38105483808
terminal FAILURE (31/33 loose native/UI); CodeQL 38105483850 and scripts
38105483489 SUCCESS. Mouse down/up settling did not solve the two input cases.
Retained log: artifacts/pointer-states-ui-38105483808.log. No local GUI input.
Issue #10 records the full source/sync/find/keys acceptance. The next implementation
adds a native read-only literal source pane (including hidden frontmatter), persisted
visibility/preferred width, both-side width bounds and the same committed reload snapshot.
This does not complete synchronized headings/scroll/collapse, paired find or vim keys.
The browser's scripts/messages/host objects remain disabled. New actual UI test checks
literal hostile source, read-only state, saved settings, reload and hiding.
166 core tests/100% line coverage, zero-warning build and formatting pass locally.
Hosted acceptance and physical source resize remain required.
Fixture-only MARKDOWNVIEWR_INPUT_TRACE=1 records native pressed/released/capture-lost,
outline drag events and frontmatter clicks without changing routing or logging content.
This tests whether WinUI receives the exact physical events; no third timing workaround
or weakened outcome assertion. The next exact hosted handles must be observed.
## Native events captured and movement correction
9ff820f4773c54573c1d24028ba489328bf2e9c6 CI 38106466328 loose native/UI
finished 31/34: two prior input failures plus SourceText accessibility lookup.
Build/format/scripts/security/CodeQL passed; installed producer now terminal FAILURE.
Retained exact log: artifacts/native-event-source-ui-38106466328.log.
Trace shows Markdown press handled and release unhandled with no click. Outline
DragStarted and DragCompleted occur, but no DragDelta. The pinned FlaUI 5.0.0
Mouse.Position/MoveTo implementation uses SetCursorPos, while Down/Up use SendInput.
Follow-up injects a real absolute MOUSEEVENTF_MOVE through the existing FlaUI Win32
API, with virtual-desktop bounds/normalization and failure checks. Button states,
exact target checks, 70-pixel resize and all saved-mode/width assertions remain.
Entered/exited/outline-moved traces will distinguish the next delivery path.
Declare native source controls before WebView2 while retaining Grid.Column positions,
as the earlier verified StatusText accessibility fix did. Source test now includes a
real 70-pixel source-divider drag; normalize only native line-ending presentation.
Source/outline resize and frontmatter remain unverified until exact hosted results.
All 9ff820f producers are now terminal; the follow-up may be pushed without cancelling them.
Native paired-find research retained in artifacts/native-find-notes.md; full scope active.
## Verified native input/source and paired find increment
4bf0b81b85e99a9f583c5e60b7276e49b423b114 CI 38106991467 completed SUCCESS.
Exact TRX: 34/34 loose native/UI, 34/34 published, stable/dev 33/33 each; no skips.
Real frontmatter switching, outline resize and literal-source/read-only/reload/physical
resize all pass. CodeQL 38106991641/scripts 38106991033/security 38106991494 passed.
Four payload gates verified 536 files; stable 0.1→0.4 and dev beta.1→beta.2 probes
called AppKit apply/restarted within their channels. Retained artifacts:
artifacts/native-move-installed-38106991467, plus native/installed verified job logs.
These synthetic unsigned fixtures remain distinct from production signing proof.
Next increment implements paired find: pure bounded literal source positions/counts,
native WebView2.Find with suppressed default dialog, Ctrl+F toolbar, wrapping controls,
Enter/Shift+Enter/Escape, actual source selection and query/page lifecycle guards.
Search temporarily reveals sections; closing restores prior/explicit choices. Browser
feature accelerators are disabled so the host owns its commands; scripts/messages/host
objects stay disabled. No new dependencies. 179 core tests pass with 100% lines; build
zero warnings and format pass. New hosted tests exercise physical Ctrl+F typing from
browser focus, source provider ranges, native viewport wrap, collapse restoration,
literal hostile-looking terms and reload/empty counts. Physical local-fragment reveal
also added. No local UI input. Observe exact new hosted handles after signed push.
Source heading/scroll/collapse synchronization and vim keys remain issue #10 scope.
Full PRODUCT.md, editor/print/multiwindow/activation/reference/standards/signing work
remain active. Do not claim new find acceptance or completed port before its evidence.
## Find shortcut and fragment acceptance correction
4f4280921cfc87551843a03e4067e6dfe7456ceb CI 38108596329 is terminal FAILURE:
36/38 native/UI and published tests pass. Installed channels were not reached.
Build/test, format, CodeQL 38108596346, scripts 38108595778, security 38108596376
and PR gates pass. Source selections, literal queries, real viewport wrap, reload
and temporary section restoration pass. Ctrl+F from browser focus did not open find;
fragment testing selected the visible sidebar label instead of its hidden browser
heading. Retained logs: artifacts/paired-find-ui-38108596329.log and
artifacts/paired-find-installed-38108596329.log.
Follow-up moves Ctrl+F to the always-loaded root accelerator and keeps the physical
browser-focus key test. Fragment lookup now requires the generated browser heading
ID and name; physical link click/reveal assertions remain. Also correct one ADR
heading's Markdown spacing. Neither correction is verified until hosted acceptance.
Full PRODUCT.md scope remains active; no local desktop input or release publication.
## Owned browser keyboard integration
7d7c712c6365fd6a036c572de3b51b49c6daa1ea CI 38109196556 is terminal FAILURE:
36/38 native/UI and published tests; installed channels not reached. Ctrl+F still
never opens find, despite a loaded root accelerator. Fragment collapse is now
correctly observed, but pointer hit is not the exact link/text child. Keep those
outcome assertions and retain owned-window screenshot, UIA, hit/focus diagnostics.
The follow-up installs WH_GETMESSAGE only on the current app UI thread, requires
the active owned window/child and Ctrl without Shift/Alt/Windows, and consumes
only that find command before browser dispatch. It queues the existing OpenFind,
forwards every other message, and removes the callback on window closure. There
is no global hook, browser bridge or search/document text logging. Host keyboard
acceptance remains pending. Exact logs: artifacts/window-find-ui-38109196556.log
and artifacts/window-find-installed-38109196556.log.
Separate local editor work is tracked by issue #11: literal ProcessStartInfo
ArgumentList, persisted executable selection and native controls. Its 195 core
tests/100% lines pass; that work is not included in this keyboard correction.
## Configured editor increment prepared locally
Issue #11 tracks editor launch and the broader reference list/folder parity audit.
The local increment adds a sanitized persisted executable path, native configure
Save/Cancel and open commands, missing-editor/document status, and one literal
ProcessStartInfo.ArgumentList value with UseShellExecute=false. No arguments or
source content determine executable selection; operational logs omit paths/content.
195 core tests pass with 100% measured lines; final native build has zero warnings
and formatting passes. Three new hosted tests use the existing test-only UpdateProbe
in an environment-selected capture mode; it never reads document contents. They
verify actual process arguments/working directory, saved configuration, invalid
selection/cancel and an editor removed after selection. No local GUI input occurred.
This editor work remains separate from pushed keyboard head 2292603 until its
live CI 38109719180 ends. CodeQL 38109719207 remains under observation;
scripts 38109718804, security 38109719182 and PR gates 38109719194 passed.
Keyboard/fragment and editor hosted acceptance remain unverified; full scope active.
## Browser link provider correction
2292603569d5a78b214933e185842f6cf233010f native/UI job 114382576572
finished 37/38: physical Ctrl+F from browser focus, actual paired source selections,
wraparound, viewport navigation and reload/collapse cases pass. Fragment diagnostics
show pointer (311,128) inside the exact link bounds (266,119,91,19), while UIA
ElementFromPoint returns owned DocumentView rather than its composed hyperlink.
Retained log: artifacts/thread-find-ui-38109719180.log. Installed producer still
requires observation before claiming channel acceptance.
The fixture now requires the pointer inside that exact link rectangle and the hit
provider to be the owned DocumentView or exact link/text. It retains the physical
Down/Up click, hidden generated-heading lookup and revealed heading/body assertions.
Those actual navigation outcomes must pass; this does not substitute bounds or
provider identity for successful link activation. No local GUI input occurred.
Local editor increment signed as 574b57432a11b04d67085cbb0203b757ea85b1f4,
not pushed while the existing producer is live. 195 core tests/100% line coverage,
zero-warning build/format/docs/hooks pass. Headless capture-fixture self-check
preserves one literal Unicode/metacharacter argument; native launch/UI acceptance
still requires the prepared three hosted tests. Full PRODUCT.md scope remains.
## Same-document fragment event correction
9edd982 native job 114383828733 in CI 38110140930 completed 40/41:
all configured-editor launch/validation/missing-file tests and paired-find tests
pass; physical fragment click does not reveal the hidden heading. Microsoft's
navigation contract says same-document fragments do not raise NavigationStarting.
Use SourceChanged for those events and a pure exact-document/query-free/in-range
generated-heading policy. Core now passes 206 tests/100% lines. Authored scripts,
messages and host objects stay disabled; fragments select only bounded heading reveals.
After a valid reveal, remove only this document's fragment using guarded host
history.replaceState so clicking the same link after recollapse can reveal again.
The physical test repeats collapse/link/reveal and retains owned point/bounds checks
and actual heading/body outcomes. Hosted acceptance remains required. Retained log:
artifacts/editor-find-ui-38110140930.log. Existing installed producer is still
being observed; no new push supersedes a live producer. Full product scope active.
## Vim increment prepared after fragment correction
Pushed fragment-event head 5a9af6328f490eda4c67d1ab6aa58a161bab46a5:
CI 38110726381 native/UI job 114385547259 SUCCESS, 41 tests. This includes
same-fragment reveal after recollapse and all configured-editor/paired-find cases.
Build/format, CodeQL 38110726336, scripts 38110725744, security 38110726260
and PR gates 38110726369 passed. Installed producer 114385547073 now SUCCESS;
retained TRX proves published 41/41 and stable/dev 40/40 each, zero skips. Four
payload gates verify 536 files per payload. Evidence is retained in
artifacts/fragment-installed-verified-38110726381 and verified job logs; fixtures
remain synthetic/unsigned and do not prove production signing. Prior 9edd982 is terminal
40/41 native/published; installed channels were not reached.
Local next increment implements j/k, Ctrl+d/u, gg/G, ]]/[[, slash, n/N and Escape;
Windows Ctrl+F owns native find and Ctrl+B reverse-page remains. Core now passes
239 tests/100% lines. Prefixes use injected monotonic milliseconds, expire after
500 ms and reject held-key imitation; lifecycle resets and focus/Alt/Windows/Shift
controls preserve native typing. The existing owned-thread adapter translates only
reading keys using the actual layout without mutating dead-key state, sends bounded
enums and rechecks focus on queued dispatch. No authored script/message bridge.
Three physical-key tests measure real browser viewport/heading/find behavior and
query typing; browser scroll provider lookup is now scoped inside DocumentView so
source scrolling cannot satisfy browser assertions. Hosted acceptance is pending.
No local GUI input, merge/tag/release or Azure attempt. Full PRODUCT.md remains;
source heading/scroll/collapse sync, broader editor/reference/theme/rendering/link
parity, print, windows/activation/recents, standards inventory/review and production
signed stable/dev final bytes remain. Preserve Darin's MIT navigation attribution.
## Vim producer focus evidence and physical fixture correction
Current dfc41a7 / CI 38111842026 native job 114388847556 finished FAILURE:
41/44 pass, zero skips. All existing tests passed; the three new vim cases failed
before their first actual reading command. Opt-in native focus logs report an
empty WinUI focused element or ToggleSection:Button while UIA reports a Pane.
UIA DocumentView.Focus alone did not establish WinUI reading focus. Exact native
log retained at artifacts/vim-ui-38111842026.log. Build/format, CodeQL, scripts,
security and PR gates passed. Installed producer 114388847436 now terminal
FAILURE; retain its log at artifacts/vim-installed-38111842026.log.
The fixture correction performs real MOVE/Down/Up inside the exact owned browser
bounds/provider before virtual-key sequences. All actual viewport, generated
heading, hidden descendant, paired find and native query-typing assertions remain.
No app focus guard is widened, no outcome is substituted and no local desktop
input occurs. The previous producer is terminal before the next push.
Full PRODUCT.md scope and deferred production signing remain unchanged.
## Source provenance foundation after native vim acceptance
6bf125d / CI 38112298176 native job 114390204742 now SUCCESS: 44/44,
zero failures/skips, actual physical reading clicks/keys and viewport/heading/find
outcomes. Duration 2m23s. Build/format, CodeQL 38112298279, scripts 38112297896,
security 38112298300 and PR gates 38112298282 passed. Exact native log retained
at artifacts/vim-focus-ui-38112298176.log; installed job 114390204514 is still
observed before any subsequent push. Previous dfc41a7 published suite timed out
at 180 seconds before final TRX; installed channels were not reached.
The next local increment maps DocumentOutline.SourceHeadings to original UTF-16
source ranges from Markdig AST headings and AngleSharp raw HTML tokens. It handles
Setext, fences, raw tags in quotes/lists/inline text, duplicate labels, native
newlines, Unicode and frontmatter. A fresh internal marker is allowed only during
mapped sanitation and stripped before output; authored markers/IDs stay denied.
Raw line lookup is indexed rather than rescanning all lines for each heading.
254 core tests/100% measured lines and zero-warning build pass. The original
snapshot and public renderer policy remain unchanged. Full scroll/heading/collapse
source UI remains unimplemented, not replaced by this foundation. No GUI input,
Azure attempt, merge/tag/release or claim of production signing.
## Verified vim channels and indexed source line ends
6bf125d / CI 38112298176 is fully SUCCESS. Retained TRX proves native/published
44/44 and installed stable/dev 43/43 each, zero failures/skips. Evidence retained
in artifacts/vim-verified-installed-38112298176, native and installed job logs.
Four payload gates verify 536 files per payload; update probes apply/restart both
channels. Fixtures remain synthetic/unsigned and do not prove production signing.
All exact prior producers are terminal before this source increment's push.
Local signed source-provenance a0d8fe7 has a further tested indexed-line-end fix:
255 core tests/100% lines and zero-warning build/format pass. A 300-heading raw
single-line regression verifies distinct offsets with one shared line end. Global
line ends are indexed once; per-heading lookup is logarithmic. This avoids a
quadratic full-remainder scan for adversarial long raw HTML lines. The source
snapshot remains immutable; no new capability/dependency is introduced. Update
PRODUCT/TODO with actual vim proof; source synchronization UI stays pending and
full scope remains active. No local GUI input, Azure attempt, merge/tag/release.
## Current native newline failure and correction
ddb98ac / CI 38114492408 native job 114396569276 finished FAILURE: 42/46,
zero skips. Existing paired source selections shift by two characters after two
line breaks; actual selections are pha** and *[x] followed by CR. WinUI's text
provider uses one CR per line break, not CRLF. The new model now normalizes display
to CR, subtracts only original CRLF extra characters and retains canonical source
positions. This also corrects heading geometry indices. The fold fixture compares
literal Setext text after newline normalization; all hide/reveal/find/reload and
actual viewport/physical-wheel assertions remain. Exact native log retained at
artifacts/source-sync-ui-38114492408.log. Build/core/format, CodeQL, scripts,
security and PR gates passed; installed producer is still observed before a push.
No local desktop input or weakening of coverage/selection/viewport checks.
## Native CR acceptance and remaining provider scroll
90b5406 / CI 38114958970 native job 114397920477 finished FAILURE: 45/46,
zero skips. Exact source find selections, nested fold/find/reload restoration and
reader-to-source heading alignment now pass. Only the source-to-reader assertion
after an unfocused native ITextRange.ScrollIntoView fails; the log reports no new
source geometry/event after that request. Evidence retained at
artifacts/source-sync-cr-ui-38114958970.log. The fixture now physically focuses
the owned SourceText and verifies its actual middle-heading glyph alignment
before requiring browser alignment. Original viewport bounds and physical wheel
assertions remain; numeric diagnostics also include source offset changes and
previous offsets. This is not yet verified. Observe installed producer
114397920229 before any next push. No local GUI input or Azure attempt.
Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

+183
View File
@@ -0,0 +1,183 @@
# Reference fidelity audit
Reference: [Darin Kelkhoff’s markdownViewr](https://github.com/darinkelkhoff/markdownViewr/tree/6d08bc9d27d7d7b2c2fa95bb282da330b8cf9021).
This records evidence and compatibility boundaries; it does not certify release readiness.
## Rendering and CSS
The upstream `Resources/template.html` and `CSSHelpView.swift` were retrieved
by immutable Git blob IDs and verified against their Git hashes. Previous cached
copies differ only in newline/BOM representation. Verified template blob: `4af334740dc345ad1292e3afac06bc9c12020a8b`;
CSS-help blob: `9b85b1fac8aca3e8542375b2420a6e0a0bbdbadc`. The screen-CSS declaration audit
retains `artifacts/pinned-css-audit.json`: all 47 shared selectors have matching
upstream declarations, accounting for later rules that override individual properties.
The Windows task-checkbox rule is an additional GFM adaptation.
`#content` remains the scroll host; `#content-inner` remains the document body.
Original palette/font variable names remain available in all 20 themes. Theme/global
CSS and reading-width overrides cascade separately. `DocumentStylesTests` checks
actual parsed declarations, containers, passive classes and print isolation;
those checks do not prove every browser’s painted layout.
## Native Windows panes
Contents, source and dividers are native WinUI controls. Upstream `#toc`,
`#raw-source`, `.raw-h`, `.raw-ellipsis` and browser-resizer CSS selectors do not
style these controls. Document CSS styles the rendered reader; source colors/fonts
come from the document palette and Windows light/dark/high-contrast handling.
Do not advertise arbitrary upstream pane-CSS compatibility.
## Heading, fold and find evidence
Parser identities retain original UTF-16 offsets through frontmatter, Setext,
raw multiline/same-line HTML and fenced pseudo-headings. A reproduced multiline
header-fold defect is fixed in `bd3d72a`; 589 core tests and 100% measured lines pass.
Its focused hosted suite passes 21/21 and full native 99/99, zero skips, including actual raw-heading jumps,
literal folds, paired find/cancel and watched reload. That head’s published suite timed out after two initial print-fixture failures;
installed lifecycle acceptance was not reached. The next pushed53755ab passes
focused21/21 and native99/99; its installed job remains live at this observation.
## Remaining release evidence
Complete current distributed acceptance, actual Default Apps/Explorer selection,
concurrent stable/dev isolation and owner design review. Retain real screenshots
and print/PDF output. Final signing, genuine maintainer review and publication
remain required; fixtures and CSS comparisons do not replace them.
## Owner-provided Mac design reference
Eight screenshots clarify the General/Editors/Themes/Markdown/Printing grouping,
aligned settings values, short explanations, palette swatches and Enabled/default
choices. Verified Windows gaps are tracked in issue #21: effective default-viewer
status in General, configurable Contents wrapping/depth markers, clearer theme
management/default semantics and a dedicated Printing page. Preserve the owner’s
Windows toolbar/default-pane choices. Private screenshots and document contents
are not reproduced here; this comparison does not certify the redesign.
A valid multiline raw heading within a Markdown paragraph exposed a separate
source-fold defect. Actual inline parser nodes now retain the complete header;
592 core tests pass with100% measured line coverage. The existing runtime fixture
now exercises this variant, with fresh hosted execution still required.
## Installed registration and coexistence evidence
Exact53755ab / CI38182128299 passes native/published99/99 and installed
stable/dev98/98 each, zero failures/skips. Actual registration readbacks verify
five extensions before/after each real AppKit update and owned removal with
existing defaults preserved. Separate concurrent stable/dev windows/profiles,
isolated updates and stable uninstall preserving the live dev edition pass.
Retain the actual TRXs and coexisting-channels.json under
artifacts/current-installed-evidence-38182128299. The fixtures are unsigned and
do not establish OS chooser/Explorer interaction or production signature trust.
## Current native regression findings
Exactb5e519e focused19/22 and native98/100 fail, zero skips; the new General/
Contents options case passes. A line-start closing raw tag is a separate Markdig
HTML block; a reproduced correction now retains complete inline title syntax
across consecutive blocks.598 core tests pass with100% measured line coverage.
A tooltip appeared during the later Contents click; interception was not proved
and placement changes did not resolve the failure. Local trim-only tips retain
the intended full-title UX. Exact picker1148/Edit exists in owned enumeration despite
the provider lookup failure; local acceptance uses that same owned identity.
Retain current-focused-b5e519e.log/current-native-b5e519e.log and focused TRX/
Settings screenshot bundle. Fresh hosted runtime remains required.
Printing now has a locally prepared native Settings page sharing the existing
output editor and checked saved defaults. Apply/Restore/closing and concurrent
sibling draft preservation have compiled actual native cases, not execution
proof. Full owner design acceptance and production signing remain outstanding.
Exactb5e519e distributed job is now terminal: published98/100, zero skips, with
the same raw-inline fold and physical source-click/alignment failures; installed
lifecycle phase not reached. Retain its actual published TRX under
artifacts/current-installed-evidence-38184378762. Signed localec78b50/7fa9f77
require fresh exact-head native/distributed acceptance after the next push.
## Theme manager and current acceptance
The pinned Mac Default checkbox writes ThemeManager.activeThemeName; its windows
can keep their own current selection. Windows continues its existing shared saved
DocumentTheme contract, applying committed choices to open readers and new
windows. The native manager now makes that behavior explicit through a default
radio control, five palette previews and textual state labels. No second saved
preference or silent migration is introduced. Actual native palette-pixel/default/
save/reopen acceptance is compiled and pending, including a Settings screenshot.
Exactb2ecc8c focused23/24 and native101/102 fail, zero skips; only physical
source/reader alignment fails. Both Printing cases, raw-inline folding, picker
cancellation and Contents presentation pass in those completed suites. Published
acceptance exceeds the unchanged600-second bound with no final TRX; installed
lifecycle is not reached. Actual logs/artifacts are retained. Closing all fixture-
owned windows before FlaUI's unchanged close/kill fallback addresses repeated
five-second teardown waits; fresh whole-run evidence is required.
The remaining repeated-activation candidate restores the trusted DocumentHeading
Tag on the new Contents content Grid. ItemClick identifies its clicked native
FrameworkElement; an already selected row produces no SelectionChanged event.
The unchanged actual physical source/Contents regression must verify this on the
next head. Pointer diagnostics are client coordinates, not screen-space bounds;
the earlier direct coordinate/tooltip-interception inference was not established.
## Exact current native proof and placement finding
Exact9d5c0d1 / CI38188145397 focused26/26 and native106/106 pass, zero skips.
Actual repeat-click/source geometry, palette pixels/default/save/reopen, future
schema export, trimmed tooltip/physical activation, Printing and raw-inline cases
pass. Retain actual TRX/screenshots under reader-regressions-38188145397 and both
current native/focused logs. Current distributed acceptance remains live.
Actual Settings screenshots reveal the window extends beyond the monitor despite
its bounded size. Local FitWindow now centers the resized window in its monitor
work area; the existing native layout test adds desktop containment. Build/format
pass; fresh rendered visibility and full runtime acceptance remain required.
## Authored HTML extension correction
The immutable MarkdownDocument.swift blob212caf9e8fcffdc7bb7e78c739402cd8d6449562
confirms protected pre/code regions, visible raw-text post-render extensions and
the body-only subscript preprocessing boundary. MarkdownExtensions.swift
c6a2d3f9931580eaab1fdef17661632f6e9c71d0 and ImageInliner.swift
5d7263613ebf96c43565a8f793d8f2dc6917e846 were also retrieved and Git-hash verified.
Four pre-fix core cases reproduce extension formatting inside authored inline
code. The existing literal-boundary pass now protects actual code/pre tags and
restores formatting after real closing tags across HTML blocks. Raw block visible
text receives enabled formatting after source annotation; tokenizer spans preserve
attributes/comments/code and entity spelling. Windows protects all four syntax
families in authored code, including subscript that the original body preprocessor
can rewrite before its HTML code guard. Metadata matches the reference
post-render/body-only subscript distinction.642 core tests pass with100% measured
lines, zero-warning Release and full format pass. The existing browser extension
case now checks distinct inline/block code peers and a raw-heading label through
preference changes; focused CI also includes this case. Runtime remains pending.
The preceding exactc3c735e / CI38190828814 is terminal success: native/published
106/106, focused26/26, installed stable/dev105/105 each, zero skips. Actual
complete Settings screenshots and coexistence/registration/update evidence are
retained. Those results predate the new renderer correction and do not certify it.
## Local images and navigation audit
The pinned Mac WebView uses a file-directory base URI and delegates clicked links
to NSWorkspace. Windows deliberately retains its isolated HTTPS namespace and
bounded physical folder access. Same-document fragments jump/reveal native
headings; HTTP(S) links use Windows after an actual user gesture. Supported local
Markdown/text file links now open native windows, with duplicate focus, independent
watching and the inherited physical-folder check on every reload. They never
launch arbitrary files or protocols. Core URI tests and10 actual filesystem tests
pass;662 core cases retain100% measured lines. Full Release/format pass. The new
physical browser-link/duplicate/watcher/junction-reload case is compiled/discovered,
not executed;109 native cases now discovered.
Image requests support PNG, JPEG, GIF, BMP, WebP, SVG, AVIF and ICO through the
same checked file handle and16MiB guard. Remote/absolute/traversing/aliased-outside
requests are denied; browser paint/PDF fixtures prove representative local output.
The Mac TIFF MIME mapping is not advertised as Windows browser support. Other
protocol handlers and access outside the document folder remain intentional Windows
security boundaries, not claims of arbitrary NSWorkspace equivalence.
The preceding exact4d34656 / CI38193736344 is terminal success: focused27/27,
native/published106/106, installed stable/dev105/105 each, zero failures/skips.
Actual raw HTML/inline/block-code browser acceptance and channel lifecycle outputs
are retained; those results predate the new local-link implementation.
+18 -11
View File
@@ -1,10 +1,9 @@
# Windows release signing
Application releases require Authenticode signing. The generated caller starts
with `sign: false` so it remains compatible with its currently published builder,
but its preflight blocks publication until the reviewed signing builder is
published, pinned and configured. The source template's content-only NuGet package
does not contain application binaries and uses a separate release workflow.
Application releases require Authenticode signing. The caller enables `sign: true`
with an explicit six-file ownership catalog and pins the reviewed standards v1.1.0
builder. Configuration and final-signature checks stop publication on failure.
The source template's content-only NuGet package uses a separate release workflow.
## Configure the protected environment
@@ -12,7 +11,7 @@ Use an existing authorized Artifact Signing account and Active Public Trust
profile. Verify its deployed region/endpoint, actual tenant GUID and full
certificate publisher subject. A subscription ID or directory domain is not the
tenant GUID. Each repository needs an approved CI identity, GitHub OIDC trust
restricted to `repo:Owner/Repository:environment:release`, and the Artifact Signing
restricted to the repository's actual release-environment OIDC subject, and the Artifact Signing
Certificate Profile Signer role scoped to its profile. Owner/Contributor alone
does not grant signing permission. Preserve other identities and assignments.
@@ -36,11 +35,10 @@ signing service. Never export a PFX or put administrative passwords in CI.
## Enable the reviewed builder
Pin a published shared builder that supports an explicit `signing-files` catalog
and verifies the final shipped bytes/signatures. Standards v1.0.0 contains gclo's
Velopack signing fix, but lacks this catalog interface; do not enable a partial
path or pin unpublished commits. After the prepared builder is reviewed and
published, update the caller's full commit SHA/version comment and add:
The release caller pins standards v1.1.0 at
`93b1d6163a4c466ae443e98b62786fc4d80d33bd`. Its reviewed builder supports the
explicit ownership catalog and verifies final shipped bytes/signatures. The
configured catalog is:
```yaml
sign: true
@@ -69,3 +67,12 @@ trusted signatures, SHA256 digests and RFC3161 timestamps. Verification failures
must stop publication. For future MSIX packaging, its Publisher must match the
verified subject. Local signing uses Microsoft's Artifact Signing Client Tools
and the installed SignTool/dlib paths with an authorized identity.
MarkdownViewr-specific verified OIDC claims and the precise remaining Azure
administrator steps are in [azure-signing-admin.md](azure-signing-admin.md).
This repository uses immutable owner/repository IDs in its subject; a copied
name-only trust from gclo would not match. Azure resource/identity/role and protected release variables are verified. The
local six-binary and final installer/portable/update-package signing proofs pass.
The new CI identity and production artifacts still require the protected release
run; local administrative signing does not prove CI OIDC.
See the administrator runbook for actual evidence and remaining dependencies.
+1 -1
View File
@@ -9,7 +9,7 @@ hooks, and run [CONTRIBUTING.md](../CONTRIBUTING.md)'s checks. Node 24 and Power
The default command works offline and writes thirteen non-secret API requests:
```powershell
./build/Initialize-GitHubRepository.ps1 -Repository KofTwentyTwo/windows-app-template
./build/Initialize-GitHubRepository.ps1 -Repository KofTwentyTwo/MarkdownViewr.Windows
Get-Content artifacts/repository-setup.json
```
+20
View File
@@ -0,0 +1,20 @@
{
"schemaVersion": 1,
"badge": {
"applicability": "required",
"reason": "Public open-source Product enrolled as project 15417. Evidence-backed maintainer assessment remains required; enrollment does not establish passing status.",
"projectUrl": "https://www.bestpractices.dev/en/projects/15417",
"evidence": "docs/security/best-practices.md"
},
"fuzzing": {
"applicability": "required",
"reason": "Untrusted Markdown/HTML/frontmatter, source ranges/folds, resource URLs, imported theme JSON and activation JSON are parser boundaries. Seeded mutation targets verify unsafe acceptance as well as exceptions.",
"targets": [
"tests/MarkdownViewr.Windows.Core.Tests/SecurityFuzzTests.cs",
"tests/MarkdownViewr.Windows.UiTests/ActivationFuzzTests.cs"
],
"command": "dotnet test tests/MarkdownViewr.Windows.Core.Tests -c Release -p:Platform=x64 --no-build --no-restore --filter Category=Fuzz",
"workflow": ".github/workflows/fuzz.yml",
"evidence": "docs/security/fuzzing.md"
}
}
+30
View File
@@ -0,0 +1,30 @@
# OpenSSF Best Practices assessment
## Scope and enrollment
MarkdownViewr.Windows is a public open-source Product; K22-REPO-12 requires real enrollment and an evidence-backed assessment. Maintainer: @KofTwentyTwo (James Maes). This entry covers the Windows repository, separately from Darin Kelkhoff's macOS project.
Registered on 2026-10-12 as [project 15417](https://www.bestpractices.dev/en/projects/15417), with repository and home page both pointing to this GitHub repository. The Metal passing assessment was initially **18% complete** after provider import. Registration and a progress badge do not establish passing status.
Reassess by 2027-01-10 and before each MINOR/MAJOR release. The maintainer must confirm actual operating practices and incomplete criteria.
## Evidence
| Area | Evidence and limits |
| --- | --- |
| Identity/license | README, MIT LICENSE, CONTRIBUTING, retained author/vendor notices; standards inventory merged in PR #36. |
| Documentation/change control | User/developer guides, PRODUCT/ADRs, linked issues, signed/DCO commits in app PR #3. Final-head human acknowledgement remains required. |
| Security reporting | SECURITY.md, private GitHub reporting, response/support policy, and threat model. Actual response practices require maintainer attestation. |
| Build/test | Locked restore, zero-warning build, analyzers, 100% measured core coverage, parser campaigns, native/published/installed acceptance. Tests do not certify arbitrary documents. |
| Supply chain | Full-SHA action pins, dependency review/audit, secret/workflow/CodeQL scans, component credits, bundled notices. |
| Releases/crypto | Current installed fixture updates/coexistence pass. Local signing/distribution proof passes; production CI OIDC signing and published stable/dev assets remain pending. |
| Governance | Single-maintainer policy is explicit. Automated review is not independent human review; acknowledgements must be genuine. |
## Remaining assessment actions
1. Review each provider criterion against repository evidence and actual operations; mark incomplete items honestly.
2. Confirm security-response and review practices personally.
3. Complete production release verification before claiming release-related criteria.
4. Save the evidence-backed assessment on the provider, with any required agreement confirmed by the maintainer.
No passing badge, approved exception, independent reviewer, or completed production release is claimed by this document.
+68
View File
@@ -0,0 +1,68 @@
# Parser fuzzing assessment
## Scope and properties
K22-TEST-25 applies to this public document reader. Owner: @KofTwentyTwo.
The native .NET/xUnit mutation harness uses the actual parsers and independent
DOM/path/source properties, with no new dependencies. It is deterministic
property fuzzing, not coverage-guided instrumentation or an exhaustive proof.
The existing core coverage gate remains unchanged.
| Target | Inputs and independent assertions |
| --- | --- |
| Markdown/HTML/frontmatter/extensions | Mutated synthetic markup; parsed output has no executable elements, event handlers or authored host attributes; checkboxes remain disabled. URLs use browser HTTPS-base semantics. |
| Source ranges/folds | Actual parser headings have bounded indices/offsets; literal source/copy preserves original newlines after fold/unfold; generated selection ranges fit the committed display. |
| Resource/heading URLs | Accepted once-decoded paths resolve within a synthetic root; foreign hosts and queried document commands are denied; heading targets remain bounded. Native open-handle containment has separate filesystem tests. |
| Theme JSON | Mutated real built-in schemas; invalid imports reject explicitly; accepted title/CSS cannot escape their HTML containers or introduce executable DOM. |
| Activation JSON | Real generated schema and validator; accepted envelopes contain only known properties and bounded absolute literal paths; unknown command fields, malformed syntax and excessive/invalid paths are rejected. No pipe/window/process is opened. |
The two target files and executable CI command are declared in
[assurance.json](assurance.json). Generated document/theme inputs are at most 4096
characters; activation envelopes have at most 144 small synthetic paths. No
attacker script, real document, credential or profile is used. No input causes
browser execution, network requests or native launches.
## Campaigns and replay
Run from the repository root with the pinned .NET 10 SDK on Windows:
```powershell
dotnet restore MarkdownViewr.Windows.slnx -p:Platform=x64 --locked-mode
$env:K22_FUZZ_ITERATIONS = '500'
dotnet test tests/MarkdownViewr.Windows.Core.Tests -c Release -p:Platform=x64 --filter Category=Fuzz
dotnet test tests/MarkdownViewr.Windows.UiTests -c Release -p:Platform=x64 --filter Category=Fuzz
# Weekly and before every MINOR/MAJOR release, repeat both commands with 10000.
$env:K22_FUZZ_ITERATIONS = '10000'
```
Each target uses seeds 22023 and 99173. PR/main campaigns run 500 iterations per
seed/target (5000 generated property evaluations total); weekly and every release
reuse run 10000 (100000 total). `fuzz.yml` runs on PRs, `main`, weekly and manual
requests; release.yml calls it and requires success before publication. The
`fuzzing` check is declared in the repository protection plan; apply and verify
that live protection after its first hosted run. The job has a 15-minute bound;
iteration configuration outside 1–100000 fails explicitly.
Failures retain seed, target, zero-based iteration and JSON-escaped synthetic
input in TRX/CI logs. To replay the sequence, use the same checkout/runtime/seed
and at least iteration+1 operations (a shorter budget does not reach it). Minimize
the input into a named regression before changing an assertion or production
code; retain original failure evidence. Genuine vulnerabilities use
[SECURITY.md](../../SECURITY.md), not a public issue containing private data.
## Evidence and review
Prepared 2026-10-11 by Codex; maintainer assessment pending; reassess by 2027-01-09,
on parser-boundary changes and before MINOR/MAJOR publication. The long local
campaign exposed an oracle error: System.Uri treats backslash input as a Windows
file URI even with an HTTPS base. AngleSharp's browser URL model preserves the
actual HTTPS semantics; a minimized regression still requires resource denial.
No production vulnerability is established from that oracle failure. Original
failed TRXs are retained; no existing test/assertion/deadline was removed.
Corrected core campaign passes 9/9 at 10000, zero skips (80000 generated inputs plus
the minimized regression). Final activation passes 2/2 at 10000, zero skips (20000
envelopes). Full core 677 tests/100% measured lines, zero-warning Release build,
formatting and 23 repository setup tests and all 64 script tests pass. Hosted execution and required-check
readback remain pending. This record does not certify
Best Practices enrollment, production signatures or general parser completeness.
+49
View File
@@ -0,0 +1,49 @@
# Scorecard process finding assessment
Prepared 2026-10-12 for the first Windows release. These findings describe
repository processes; they do not identify an exploitable shipped component.
This assessment does not fabricate a vulnerability waiver, independent reviewer,
90-day maintenance history or a passing Best Practices badge.
## Single-maintainer review and branch controls
The owner selected the single-maintainer policy. Approved
[EX-0001](https://github.com/KofTwentyTwo/standards/blob/main/exceptions/register.md#ex-0001)
applies to every KofTwentyTwo repository and records the missing non-author
human approval as an accepted risk, with compensating controls.
[Code-Review #8](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/security/code-scanning/8)
correctly counts no independently approved changesets. The main ruleset has
no bypass actors, requires PRs, signed commits, linear history, resolved threads
and seventeen Actions-bound checks, including the maintainer-owned automated
review record. The current application PR remains blocked until that actual
record exists; this assessment cannot supply it.
[Branch-Protection #5](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/security/code-scanning/5)
identifies the same unavailable independent approver, code-owner approval and
last-push approval. Keep the chosen policy and existing controls intact.
Reassess when a second maintainer joins; require independent approval then.
This is an accepted review gap under the existing exception, not maximal
Scorecard protection or proof that every historical change was reviewed.
## Repository-age finding
[Maintained #9](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/security/code-scanning/9)
explicitly reports creation within the last90 days. GitHub reports creation on
2026-10-11; recent issues, signed feature commits and actual CI evidence show
current activity. The age-based score does not establish abandonment, nor does
current activity prove long-term maintenance. Reassess on2027-01-09, after the
90-day boundary, and investigate any remaining maintenance finding honestly.
## Remaining findings and actions
Retain the current alert state until the assessment is reviewed and any authorized
disposition is recorded. Do not disable Scorecard or invent changesets to raise
its score. Fuzzing and SAST scores on the scaffold main revision must be
reassessed after the tested application is merged; native fuzz/property targets
may not be recognized by Scorecard. The actual Windows-specific Best Practices
enrollment/assessment remains open in issue#31. No earned badge is claimed.
No OpenVEX statement is fabricated for process findings without a vulnerable
component. Actual exploitable code/dependency findings still need remediation
or a reviewed, precisely scoped exception before shipping.
+408 -72
View File
@@ -1,92 +1,428 @@
# Threat model: MarkdownViewr
<!-- K22-SDLC-03. Update in any pull request that changes the attack surface, and review
in every MINOR/MAJOR release checklist (K22-SEC-30). Method: data flow + STRIDE. -->
- **Version reviewed:** first reader implementation
- **Last reviewed:** 2026-10-11
- **Method:** data flow and STRIDE; review again for each attack-surface change.
- **Version reviewed:** template readiness work · **Last reviewed:** 2026-10-10
## Product and boundaries
## 1. What the product is and does
A per-user WinUI application reads user-selected local Markdown. AppKit supplies
settings, logging, crash handling and Velopack updates. The UI-free core parses and
sanitizes Markdown. WebView2 displays the sanitized result through an intercepted
HTTPS origin. No application HTTP server or scriptable command-line head exists.
A Windows desktop application installed per user (no administrator rights) with
Velopack, updating itself from its GitHub Releases. It runs entirely on the user's
machine: it keeps preferences and logs in the user's profile and has no server
component. The template's example feature (a greeting) handles only text the user types.
Document text, HTML, links, images and filesystem aliases are untrusted. The user
selects the document; this authorizes reading it and passive images within its
physical directory. It does not authorize native commands or remote resource access.
## 2. Actors and components
## Interfaces and controls
| Actor or component | Description | Trust level |
| Interface | Control |
| --- | --- |
| Picker / literal process argument | Read-only bounded snapshot; no shell interpolation |
| Frontmatter | Bounded by document limit; complete leading delimiters only; metadata values HTML-escaped; Markdown mode uses the same sanitizer; no YAML execution or native commands |
| Markdown / raw HTML | Explicit tag/attribute/scheme allowlist; disabled checkbox inputs; no authored scripts, CSS or IDs; only bounded code-language and fixed document classes survive |
| Native print/PDF | Deliberate File/Print/Ctrl+P; ready passive document only; copied bounded settings; Windows destination picker; JSON-serialized CSS text in host-owned print stylesheet; same navigation checked after preparation; no authored command/output path; no document/path logs |
| File change notifications | Directory subscription scoped to selected path; 350 ms quiet-period debounce; replacement/deletion recovery; bounded transient retries; subscriptions released on window close |
| Theme JSON / CSS | Native editor and saved user overrides use the same bounded validation; corrupt theme settings are discarded before startup; restoration keeps separately named custom themes; embedded original palettes; bounded strict version-one parsing and validated compatible saved/import schemas with explicit newer-import consent; validated hex colors and finite sizes; code-point escaped font names; less-than signs escaped before style embedding; no network capability |
| Browser viewport | Fixed origin-checked native query; validate two finite fractions; invariant numeric restore only; authored scripts/messages stay disabled |
| Source pane | Bounded immutable snapshot plus literal native fold projection; original UTF-16 find positions map to visible native CR text; HTML/CSS/script text never enters a renderer or command interpreter; hidden pane removed from accessibility surface |
| Find | Bounded literal query; pure source positions and native WebView2 finder, with duplicate browser UI suppressed; no query enters executable code or activity logs. Serialized queries and page/navigation checks reject stale completions; existing numeric section operations reveal/restore content |
| Fixture input diagnostics | Explicit opt-in environment switch; observes handled native pointer/drag events without changing routing; logs owned control names, coordinates and button state only, never document text or keystrokes |
| Browser surface | Scripts, messages, host objects, permissions, downloads, frames and popups disabled; restrictive CSP |
| Local images | Per-document URI prefix; one decode; filename/extension policy; final open-handle containment; 16 MiB limit |
| Remote resources | Denied by CSP and resource interception |
| External links | HTTP(S) only, actual browser user gesture, external Windows handler; no document navigation outside current page |
| Heading navigation | Labels extracted after sanitation; authored identity attributes stripped; generated numeric targets only. Fixed host operations reveal/jump/collapse on the current intercepted page; authored scripts, messages and host objects remain disabled |
| Settings / logs | AppKit sanitation; per-user data root; log operational events without document content or paths |
| Updates | Repository HTTPS feed, Velopack integrity checks and production signing preflight |
| Repository / CI | Protected main and release tags; producer-bound checks; no untrusted PR checkout with privileged review token |
## Threats and verification
| Threat | Mitigation and evidence |
| --- | --- |
| Document script or native-command execution | Sanitizer regressions pass; host disables scripts/bridge; actual browser malicious-script and reader acceptance passed on 77dd04d |
| Image traversal, device/ADS paths or encoded separators | Core path-policy tests pass; denied before filesystem access |
| Junction/symlink escape or check/reopen race | Check normalized physical path on opened image handle; read same handle; real junction rejection test passes |
| Remote image tracking / resource exfiltration | No-referrer header, no document networking; actual listening-server browser denial passed on 77dd04d |
| Oversized input / image denial of service | Pre-parse character bound and image byte bound; native limit tests pass; performance acceptance remains |
| Settings corruption / UI exception | AppKit shell sanitation and crash handling; inherited foundation tests |
| Tampered installer/update | Fail-closed signing/provenance/checksum release pipeline; real Azure CI authorization and signed artifacts remain unverified |
| Privileged repository review abuse | Trusted review workflow only; current-SHA human acknowledgement remains required |
## Remaining risks and work
This is a first reader, not a completed port. Native frontmatter/outline/literal-source acceptance passed CI 38106991467 in loose, published and both installed builds. Paired find and configured editor launch passed hosted and installed acceptance; new synchronized source/collapse, broader editor parity,
print and any future browser bridge need their own controls and tests. Save watching is
implemented; hosted real browser replacement/deletion/scroll and remote-image denial pass on 77dd04d.
Local links to other documents and mailto links are currently blocked. WebView2 uses
the installed evergreen runtime; published and both installed channels passed reader acceptance on 77dd04d and editable themes on 30ca094. Paired-find keyboard/source/viewport/hostile-query acceptance passed on 5a9af63. New source synchronization acceptance remains required.
Historical EX-0002 does not authorize unsigned production publication. Azure setup
and real final-artifact signature validation are deferred; an active profile alone
does not establish working release signing.
## Review log
| Date | Reviewer | Changes |
| --- | --- | --- |
| User | The person running the app | Trusted for their own data |
| WinUI shell (`MarkdownViewr.Windows.exe`) | Views and startup plumbing | Runs as the user |
| Core (`MarkdownViewr.Windows.Core`) | Logic, settings, view models | Runs as the user |
| Command line (`markdownviewr-windows.exe`) | Optional scriptable head over Core | Runs as the user |
| AppKit packages | Shell services: logging, settings, secrets, updates, splash, About | Third-party code, pinned and attested |
| Velopack update service | Checks GitHub Releases, downloads and applies packages | Runs as the user |
| GitHub Releases | Update feed and download source | Untrusted network data |
| 2026-10-10 | Codex (GPT-6) | Added directory watching and guarded numeric viewport preservation; hosted reload acceptance pending |
| 2026-10-10 | Codex (GPT-6) | Replaced template-only interfaces with actual document and browser boundaries; recorded separate unit/native/browser evidence |
## 3. Data flow and trust boundaries
## Owned-window keyboard boundary
```mermaid
flowchart LR
user([User]) --> shell[MarkdownViewr.Windows.exe]
user --> cli[markdownviewr-windows.exe]
shell --> core[MarkdownViewr.Windows.Core]
cli --> core
core --> data[("%LOCALAPPDATA%\markdownviewr-windows<br/>settings.json · logs")]
shell -- HTTPS --> gh[(GitHub Releases)]
subgraph machine [Trust boundary: the user's machine and account]
shell
cli
core
data
end
```
Browser-focus shortcuts use a current-UI-thread `WH_GETMESSAGE` hook requiring the
active owned window/child. Ctrl+F remains a native window command. Vim keys require
the actual passive-browser/read-only-source focus; native text controls retain input.
Alt/Windows and extra Ctrl modifiers do not become reading commands. Only bounded
enums leave the key model; prefixes expire/reset and held prefix keys do not complete
sequences. Layout translation uses ToUnicode flag 4 to preserve dead-key state;
non-reading input is not translated. Dispatch rechecks focus. Other messages pass
through; closing removes the callback. No global hook, other-process capture,
arbitrary command, browser bridge or text logging is introduced. Fixture-only focus
traces contain native control names/types, never query or source text. Actual hosted
physical keyboard acceptance is required.
See Microsoft's [thread-hook contract](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowshookexw) and [layout conversion contract](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-tounicode).
## 4. External interfaces
## Configured editor boundary
| Interface | Direction | Data | Authenticated? | Validated where? |
| --- | --- | --- | --- | --- |
| Window input (text boxes, menus) | In | User text | The signed-in user | `GreetingService.Greet` (length limit), each feature's Core service |
| Command line arguments | In | Command, options | The signed-in user | `CliApp.Run` (unknown commands and options rejected) |
| `settings.json` | In/out | Preferences | File ACLs of the user profile | `SettingsStore` sanitizes on load; corrupt files fall back to defaults |
| Log files | Out | Operational events, no secrets | File ACLs | Logging API takes messages only |
| Data-folder environment variable | In | A folder path | The signed-in user | Used as a path only |
| Update feed (GitHub Releases over HTTPS) | In | Release metadata and packages | TLS; package hashes checked by Velopack | Velopack |
| Windows Credential Manager | In/out | Secrets, if a feature stores any | The user's Windows login | AppKit `CredentialManagerVault` |
Only deliberate native configuration selects existing, bounded, fully qualified
`.exe` editors. AppKit sanitizes at most 32 named stable-ID choices, discarding
invalid/duplicate IDs, corrupt names and executable paths. Legacy single-path
settings migrate into one entry; cancel edits only a copied list. Opening the editor requires a current
existing document and passes its full file path or selected parent folder as exactly
one `ArgumentList` value with
`UseShellExecute=false`; Markdown cannot choose an executable or supply switches.
The chosen application runs with the user's permissions and receives the document.
Missing files and native launch errors leave the viewer open; logs omit paths and
contents. Core tests cover metacharacters/Unicode, relative/script paths, corruption
and bounds. Actual fixture-process/UI acceptance passed on 5a9af63 in native, published and
installed channels; new named-list/submenu order, cancellation/removal and actual folder/file probe
launch acceptance remain pending.
## 5. Assets
## Source provenance boundary
The integrity of the installed program and its updates (an attacker who controls them
runs code as the user), the user's preferences and any stored secrets, and availability
of the app.
Source anchors originate from Markdig heading spans and AngleSharp raw HTML token
positions, with original newline/frontmatter/UTF-16 offsets. Repeated titles or
fenced heading-like text cannot select another heading's identity. A fresh private
attribute name is admitted only during the internal render and removed before any
HTML is presented; public renderer policy and authored ID/data-attribute denial
remain unchanged. Positions pair only with sanitized surviving headings and do
not grant scripts, messages, native access or resources. The original snapshot is
immutable. Core regressions cover nested raw markup, Setext, fences, Unicode,
frontmatter and forged attributes; source scroll/folding UI acceptance is pending.
## 6. Threats (STRIDE)
## Source synchronization boundary
| # | Boundary / component | Category | Threat | Likelihood | Impact | Mitigation | Status |
| --- | --- | --- | --- | --- | --- | --- | --- |
| T1 | Update feed | Tampering | A malicious update is served to installed copies | Low | Critical | Updates only from this repository's immutable, attested GitHub Releases over HTTPS; Velopack verifies package hashes; Authenticode signing once EX-0002 closes | Mitigated (signing pending) |
| T2 | Download | Spoofing | A tampered installer on a mirror | Medium | High | README verification steps: `SHA256SUMS`, `gh attestation verify`, Authenticode | Mitigated |
| T3 | Settings file | Tampering | A crafted `settings.json` crashes the app or sets invalid values | Low | Low | Source-generated deserialization, `Sanitize` on load, defaults on failure | Mitigated |
| T4 | Logs | Information disclosure | Secrets or personal data written to logs | Low | Medium | Log calls take plain messages; secrets go only to Credential Manager (K22-SEC-71); review | Mitigated |
| T5 | Install folder | Elevation of privilege | DLL planting next to the exe | Low | High | Per-user install under `%LOCALAPPDATA%`, owned by the user; no loading from the working directory | Mitigated |
| T6 | Shell | Denial of service | An unhandled exception closes the app with unsaved work | Medium | Low | AppKit crash net logs and survives UI exceptions; one-dialog-at-a-time guard | Mitigated |
| T7 | Input | Denial of service | Very long input slows or breaks the UI | Low | Low | Length limits in Core services | Mitigated |
| T8 | Repository administration | Tampering | Setup weakens protections, trusts another check producer or targets another repository | Low | High | Offline preview; exact public target/admin checks; verify official Actions identity and every context binding in apply/readback; current-PR checks; refuse conflicting rules and broad release refs; preserve reviewed plan | Local regression tests pass; live bootstrap pending |
| T9 | PR review workflow | Elevation of privilege | Untrusted PR code runs with a checks write token | Low | High | Comment-triggered workflow executes trusted workflow SHA only; no PR checkout; human current-SHA acknowledgement required | Local tests pass; hosted enforcement pending |
| T10 | Package-manager distributions | Tampering | Manifest points at wrong bytes or an unsafe executable | Low | High | Stable-only generation; ZIP identity/layout checks; actual SHA256 and provenance verification; exclude helper shims; candidate review before feed publication | Local tests and real archive/schema checks pass; feed lifecycle pending |
| T11 | Installed update smoke | Tampering | A test overwrites a real app or deletes unrelated files | Low | Medium | Unique test package ID; fresh artifacts-only output; app identity check; literal arguments; offline URL allowlist; exact process path; root/manifest checks before uninstall | Local runtime/boundary tests pass; hosted CI pending |
Only fixed host operations capture numeric scroll positions, generated heading IDs
and hidden generated bodies on the matching ready document. Exact JSON shape,
bounded array lengths, unique in-range integer IDs and finite coordinates are
required. No source text, heading label or query enters executable code. The native
adapter serializes polling, rejects stale snapshot/query/page completions and
suppresses feedback around find selections and its own viewport movements. Closing
stops the timer; hidden panes do not synchronize. Literal ellipses only affect the
presentation; canonical document bytes and independent nested fold choices remain.
Opt-in fixture geometry logs contain numeric positions/counts only.
## 7. Accepted risks
330 core tests cover editor migration/persistence/folder arguments, folding, original/native offsets and malformed numeric frames
at 100% measured line coverage. Hosted actual glyph/viewport and fold/find/reload
acceptance is pending; raw multiline/same-line heading parity still needs audit.
- EX-0002 tracks historical unsigned Windows release risk. The current application
release preflight rejects unsigned production publication. Verified deployment,
scoped CI authorization and final-artifact signature acceptance remain required;
completed publisher validation alone does not prove CI signing.
Entering read-only source now defers restoration of its prior viewport after native
focus/caret revelation, guarded by the current snapshot/window and excluding active
find. An unselected read-only caret is positioned at the first visible native character
before focus, preventing the observed later caret reset. The original post-focus
glyph assertion passes on 1c4d1ad; source-to-reader acceptance remains open. Hosted acceptance must verify actual glyph positions before/after
focus and native source-to-reader movement; numeric fixture logs contain no text.
## 8. Review log
## Reading zoom
| Date | Version | Reviewer | Changes |
| --- | --- | --- | --- |
| 2026-10-09 | template baseline | maintainer | Initial model from the Windows desktop reference architecture |
| 2026-10-10 | repository setup | Codex (GPT-6) | Added administration and review workflow trust boundaries; live proof pending |
| 2026-10-10 | check producer readback | Codex (GPT-6) | Verify official Actions identity/bindings without mutating caller plans; clarify signing gate versus historical risk |
Zoom remains window-local and receives only explicit owned native commands. Its
finite 0.3–5 scale multiplies validated font sizes with invariant numeric formatting;
line height, colors, theme JSON and custom CSS retain their existing semantics.
No authored script, message bridge or new resource capability is enabled. Navigation
uses existing matching-page viewport/collapse guards and ready-page find replay.
Ctrl zoom commands require reading focus; editor/theme/find text input retains
native ownership. 330 core tests/100% measured lines pass; actual glyph/key/reload
acceptance is pending. Native menus/toolbar do not imply production release proof.
## Print lifecycle
Reading and print styles have separate `screen`/`print` media attributes. Even
custom CSS braces cannot escape a stylesheet's media restriction. Printing never
changes reading theme data, source text, find state or generated collapse flags;
print CSS exposes folded bodies only in print media. Native source feedback pauses
through the settings/PDF operation, and geometry queries reject print-media frames.
WebView2 native print UI has no completion callback: opening it is not proof of a
successful job. Native PDF output is awaited and written only to the user-chosen
Windows picker destination. PDF export disables headers/footers; fixture acceptance
cancels print UI and renders owned generated PDFs, never submits a physical job.
The existing CSP, denied remote requests and opened-handle image containment apply.
See ADR-0003; full hosted/output/layout verification remains pending.
## Document windows and recent files
Only literal process arguments and deliberate native picker/recent commands open
documents. The App SDK multiple-file picker is bound to its owning AppWindow.Id.
A new reader receives its own initial path, browser, watcher and keyboard/window
lifetime; document content cannot open windows or invoke native menus. Reuse/focus
is restricted to matching canonical paths within the current process. Browser CSP,
local-image handle containment and passive-content policy apply independently to
every reader; closing one stops only its owned callbacks and hooks.
Recent paths remain private in the local AppKit settings file, capped at20 and
32767 characters per entry; relative paths, URLs/control characters and duplicates
are discarded. Record only successful parsed opens, never missing/unreadable files.
No document content or paths enter activity logs. Native Clear removes history,
not files. Source-generated property-delta merging preserves sibling preferences
and prevents an older window snapshot from restoring cleared paths. Core tests
cover corruption, bounds, ordering, independent arrays and stale-window merging.
Actual hosted lifecycle/clear/reload tests are pending; association/activation
registration has not been implemented or represented as verified. See ADR-0004.
## Configurable Markdown extensions
Only deliberate native menu commands change the four saved rendering preferences;
document content cannot modify them. Each asynchronous read captures immutable
options. Existing Markdig parsing generates passive mark/sup/sub/ins nodes and
preserves original source offsets. Reference caret/tilde whitespace and protected
code/newline boundaries remain literal. The sanitizer still removes authored
styles, identity/event attributes and unsafe links; mark/ins grant no native or
network access. Host-generated theme CSS uses validated palette variables.
Separate saved fields preserve sibling preference edits. Core tests cover all16
option combinations, code/escapes, hostile attributes, original source provenance
and old JSON defaults. Actual native repaint/toggle/reload acceptance is prepared
for hosted runners and remains unverified. Raw authored-HTML extension edge cases
remain part of the reference audit; no broader renderer-parity claim is made.
## Complete native typography editing
All font families/sizes and line height use native controls backed by a copied
core theme edit. Known field names, finite/ranged numbers, bounded escaped font
strings and hex colors pass the existing renderer validation before saving.
Unknown fields and invalid drafts are rejected; unrelated schema metadata is
preserved. No document content can invoke these edits. Custom CSS still cannot
close its style element, and existing CSP/resource limits apply. Core tests prove
invalid input leaves originals intact, successful edits retain all fields, and
hostile font/CSS strings preserve the HTML boundary. Actual glyph/save/cancel
acceptance compiles for hosted runners and is not yet verified.
## Shared reading appearance
Only deliberate native settings commands save global CSS and content width.
Copied drafts validate finite200–4000px widths and a128Ki-character CSS bound;
corrupt persisted values fall back safely. Settings are merged against the latest
store before broadcasting to open readers. Revision checks reject obsolete
asynchronous application; updating sibling baselines prevents later local saves
from restoring an older global preference.
Global CSS is escaped for HTML style embedding and remains in screen-only media.
Per-theme CSS retains precedence; an enabled width limit is applied last, scaled
with reading zoom. Print CSS uses its existing independent theme/layout path.
No new script, host object, message bridge or remote-resource permission is enabled;
existing CSP and denied network requests still apply.448 core tests/100% measured
lines pass. Two new hosted cases compile but have not run; actual sibling paint,
wrapping and PDF isolation are not yet declared verified.
## Shared theme management
Only owned native manager/menu/cycling commands change saved theme lists or the
active palette. Copied drafts retain cancellation; names are bounded, deduplicated
and reject control characters. Disabled themes remain editable while disappearing
from reading menus. Built-in deletion only removes settings overrides and records
a restorable name; shipped resources and document files are untouched. Restoring
originals preserves edited JSON/metadata as uniquely named custom copies.
Latest-store field merging and reader save baselines preserve unrelated sibling
preferences. Catalog/selection broadcasts and browser revision checks discard
obsolete asynchronous theme applications. Cycling uses only enabled saved order
and exact Ctrl+Shift arrows with owned reading focus; text controls retain input.
Existing schema/font/CSS validation, screen/print separation, CSP and resource
denial remain enforced.471 core tests/100% measured lines pass. Native menu/paint/
keyboard/sibling and revised restoration acceptance compile but remain unexecuted.
## Native theme transfer and copied manager dialogs
Theme JSON enters only through deliberate owned native file pickers. Serial reads
allocate at most the128Ki-character bound plus one before schema/depth validation;
UTF-8 and recognized BOM encodings retain Unicode. Invalid files fail independently
within a batch. Compatible newer schemas still validate all known colors/fonts/
sizes/CSS and require explicit native consent before import persistence. Strict
version-one parsing remains available. Saved approved versions and unknown metadata
survive rename/edit/restoration/export without enabling new rendering privileges.
Overwrite replaces only the named user override; keep-both reserves unique names
across the batch; skip/cancel leaves saved data intact. Native new/copy/edit/default
and transfer actions use copied settings across successive dialogs, with manager
Save required for persistence. A renamed editor cannot silently overwrite another
existing palette. Export writes exact validated JSON only to the user's chosen
native destination, including disabled choices. Suggested names exclude Windows
path characters and ordinary reserved device names. Errors log exception kind/
HRESULT only, never selected paths or JSON. Closing the owner stops reopening.
517 core tests/100% measured lines and zero-warning build pass.69 desktop cases
are discovered headlessly; six new actual picker/choice/export/draft/cancel cases compile
but have not run. Runtime proof remains pending. Existing CSP, resource denial and
screen/print separation remain in force; original CSS-selector compatibility is
still a required audit.
## Public Help links and post-export actions
Help destinations are fixed application metadata: the Windows repository, its issue
chooser and Discussions. Original Mac/author links appear separately in About; no
authored document path, content or credential enters these URLs. Native browser
launches require deliberate menu/link actions; the renderer capability boundary
is unchanged. Starter setup now requires/readbacks Discussions for support.
Optional PDF/folder opening uses only the destination deliberately chosen in the
owned save picker. The native exporter must succeed and Windows' PDF loader must
read pages before success/actions. Cancellation/failure cannot launch them. The
PDF and folder actions are independent; missing native handlers report an action
failure while retaining the completed export status. Logs retain phase/runtime
or exception kind/HRESULT, never private PDF/document paths or content. New actual
print/PDF/credit acceptance is still pending; the owner's handoff report remains
unresolved despite earlier hosted PDF proof.
## Native highlighted source and linked component credits
Source is committed as literal text in a read-only RichEditBox; only native
character formats are applied. No RTF/HTML source loading or authored URL
attributes are used. Explicit Default input scope disables auto-URL detection.
Parser classification runs off-thread with snapshot/cancellation guards; native
paint batches discard stale snapshot/palette generations. Selected document
accents are contrast-adjusted for shell mode; high contrast uses system colors.
Copy is a deliberate native action and emits only visible original plain text,
including Unicode and original line breaks. Folding never copies hidden bodies.
Clipboard failures report a content-free status; closing cancels background work.
Core spans/copy/contrast are verified; native accessibility/colors/clipboard and
scroll/find/fold/reload acceptance remain pending.
About project/license links are curated from restored package metadata, never
from authored documents. Microsoft binary-package license terms are identified
separately from MIT libraries. Verbatim SDK/WebView/tensor notices are copied as
ordinary application data, independent of the XAML resource index. Native Settings
owns its theme/editor dialogs and pickers; routine checked layout saves preserve
unsubmitted CSS drafts and update reader/save baselines. UI acceptance remains
required for the new settings window and exact distributed artifacts.
## Windows file registration and process activation
Installed Velopack callbacks advertise five fixed file extensions under HKCU.
Commands quote the stable current executable and literal `%1`; no shell script
is involved. Channel/executable-specific identities and command ownership checks
bound uninstall cleanup. REG_NONE `AllowSilentDefaultTakeOver` prevents silent
selection as a default. Extension defaults, UserChoice, HKLM, other registrations
and portable/development-build registration are outside these operations.
The App SDK chooses a primary per executable/profile directory. The activation
pipe permits only the current Windows user; it is an additional native ingress
for literal file paths, never browser content or native command execution. Paths
are canonicalized at the sender. The generated schema rejects unknown fields,
relative/control-containing/null/overlong paths and more than128 paths; frames
are bounded to1MiB, connection/read waits to10seconds and pre-XAML queuing to16
requests. Normal document size/resource/sanitizer boundaries still apply.
Malformed/disconnected requests are rejected and the listener continues; owned
shutdown cancels the pipe. Fixed notifications and failure kind/HRESULT logging
do not expose paths or payloads. UI-dispatched duplicate activation preserves
existing readers, including pending initial loads. Nine actual isolated adapter
tests pass; native app, Explorer and installed lifecycle acceptance is pending.
See ADR-0005 and issue #14. Same-user processes can deliberately request document
opening; this is not an authorization boundary against an already compromised
account. No IPC operation executes arbitrary commands, loads plug-ins or changes
settings, credentials or updater feeds.
The default-file Settings button is a deliberate native user action. It reads the
owned installed registration again before launching a fixed `ms-settings` URI;
the query contains only the fixed channel/executable-derived registered value
name. No document path/content can select the target or invoke this action.
Portable/unregistered previews cannot initiate it. Selection remains in Windows,
with no writes to UserChoice/defaults and `.txt` explicitly optional. Eleven
isolated adapter tests now pass; OS chooser behavior still needs acceptance.
The native General default-status read uses fixed `.md` and Windows
AssocQueryStringW/ASSOCSTR_PROGID with a1024-character Unicode buffer. Require
unchanged channel/install registration before displaying Default; missing/error
responses stay Not default or Unknown. No UserChoice hash, competitor executable
path, document content or authored native command is read/logged. Returning from
Windows Settings refreshes status without setting associations. Seven real
read-only/isolated adapter cases pass; actual OS selection still needs acceptance.
## Reference CSS, Settings drafts and document status
The attributed reference stylesheet is an embedded application resource. Rendered
Markdown remains sanitized before DocumentPage inserts it into #content-inner;
titles are HTML-encoded. Only fixed document classes and bounded code-language
classes survive sanitization. Theme/global CSS uses existing validation and
blocked-resource policies; it cannot close the style element, add native commands
or fetch unauthorized remote/local resources. Reading/print container changes do
not extend renderer message capabilities. Native TOC/source remain separate
controls; full reference selector parity still requires actual acceptance.
Long theme/editor drafts live inside the native Settings window. Back, Cancel and
window closure discard them. Validation gates explicit Save, manager child edits
remain inside its copied draft, and native file pickers/short confirmations retain
Settings ownership. A display-state change never grants document content access
to these native operations.
Footer metadata belongs to the same open handle used for the loaded snapshot.
Watcher notices, failed reloads and bounded metadata polling mark stale content;
only a successful new snapshot clears that indicator. The explicit Open folder
button uses StorageFolder and Launcher.LaunchFolderAsync with a literal document
parent directory; it does not construct shell commands. Diagnostics omit authored
content/paths. The disabled-editor tooltip reports native configuration state,
and its timer and footer polling stop on window closure. Native status/hover/
Explorer acceptance remains pending; passing pure or adapter tests is insufficient.
## Disconnected activation peers
An accepted native pipe connection remains owned until explicitly disconnected,
even when a broken read clears the managed IsConnected property. Each request
retains its current-user boundary, bounded frame/deadline and generated-schema
validation. The deterministic already-closed-peer test uses real pipe handles
and acknowledgements with a controlled continuation schedule: its pre-fix next
connection times out; after the cleanup correction it recovers. Four cases and
25runs/100tests pass locally. Exact hosted published/installed proof remains
required. No protocol, caller privileges or document/native capability expands.
## Authored HTML extension boundary
Source identities/ranges are captured before visible raw-text formatting. Actual
HTML tokenizer spans delimit candidate text; attributes, comments and code/pre
content are not rewritten. Each selected reference pattern has a one-second
regex limit, and the existing document-size guard applies before parsing. Metadata
retains the original body-only subscript preprocessing boundary. Every result
still passes the unchanged tag/attribute/scheme sanitizer and browser CSP; these
passive formatting tags provide no scripts, host messages or native capabilities.
Core regressions exercise all16 preference combinations, protected inline/block
code, comments/attribute lookalikes, entities and exact CRLF/Unicode source ranges.
Fresh hosted browser/distributed acceptance is required for this correction.
## Native local document-link boundary
Only actual user-initiated browser navigation can request a local reader. Its URI
must belong to the current private namespace, have no query and use one of .md,
.markdown, .mdown, .mkd or .txt. Once-decoded segments reject traversal, devices,
alternate streams, controls and filename aliases. No document link is dispatched
as an executable, script, arbitrary protocol or OS shell command.
The source snapshot supplies its trusted physical directory. The target's open
handle is checked against that directory before reading, and that same handle
supplies the snapshot. A linked reader retains the required root for every reload,
including watcher/extension/frontmatter refresh. A changed junction cannot replace
its committed data with outside content. Duplicate focus requires an already
committed snapshot inside the source grant. Closed source windows cannot launch
new linked readers. Existing image, sanitizer, CSP and host-message rules remain.
Core URI and actual filesystem/junction tests pass; the prepared physical-link/
reload-scope desktop regression still requires hosted execution.
## Repeatable parser assurance
The seeded targets in [fuzzing.md](fuzzing.md) exercise Markdown/HTML/frontmatter,
literal source/folds, resource URLs, imported theme JSON and activation JSON through
actual parsers. Independent properties detect unsafe acceptance as well as crashes.
Synthetic inputs never execute scripts, start native processes or touch real profiles.
The record in [assurance.json](assurance.json) does not claim coverage-guided
instrumentation, Best Practices enrollment or verified production signatures.
+18
View File
@@ -0,0 +1,18 @@
# Standards readiness
Read-only checks performed on 2026-10-11 against KofTwentyTwo/MarkdownViewr.Windows.
The full port and public release are not complete; do not add a conformance badge.
| Area | Evidence and remaining work |
| --- | --- |
| Repository settings | `build/Initialize-GitHubRepository.ps1 -Mode Validate` returns `SettingsVerified=True`: required contexts are bound to official Actions; protections, action policy and tag-only release environment read back successfully. |
| Official conformance checker | 34 PASS, 1 FAIL, 1 WARN, 3 REVIEW; full JSON retained at `artifacts/repo-conformance-2026-10-11.json`. |
| Required inventory | `K22-REPO-42` remains pending: signed inventory proposal6b4fd17 is in standards draftPR36; its checks pass. App/starter are Product with adoption-in-progress status. Review/merge is required before claiming registration in main. |
| Shared community defaults | `K22-REPO-10` warns: no owner `.github` repository. App governance/security/contributing files exist; shared owner defaults remain optional setup work. |
| Workflow reviews | `K22-CI-02/03/31` require review of PR/push tests, documented command alignment and full release gate reuse. Current callers visibly run PR/push(main) CI and the release reuses CI/security. No human review acknowledgement is inferred. |
| Test evidence | Exact9994eda passes586 core tests/100% measured lines, focused20/20 and full native98/98, zero skips. CI38176727468 is terminal: published98/98 and installed stable/dev97/97 each, zero skips; actual apply/restart passes both. Diagnostic-free764c7ac passes98/99 native/published and20/21 focused: the new multiline raw-header fold fails. Local correction passes589 core/100% lines; fresh native/installed registration acceptance remains required. |
| Release | Stable/dev synthetic installed updates call AppKit apply/restart successfully. Actual verified Azure identity/profile/signer permissions and exact signed distributed artifacts remain deferred. Owner authorized stable1.0/dev publication after all requirements pass; signing and review requirements remain unmet. |
Full acceptance remains in `docs/PRODUCT.md` and `docs/TODO.md`; maintainer review,
Best Practices owner attestations and final signing cannot be replaced by local
settings checks or green fixture tests.
+77
View File
@@ -0,0 +1,77 @@
# Using MarkdownViewr
## Open and read a document
Choose **File → Open** or press **Ctrl+O**. You can open multiple documents in separate windows, return to recent files from the File menu, or open a file through Explorer after installing and selecting its association.
MarkdownViewr is a reader. Use **Open in editor** to edit a document in your configured external editor; the Source pane is read-only. Configure named editors in **File → Settings → Editors**. A disabled editor command explains what is missing when you hover over it.
## Contents, source, and search
- **Contents** shows document headings. Choose the heading depth in the pane, select a heading to jump, or collapse sections to concentrate on the rest.
- **Source** shows highlighted Markdown beside the rendered document. Each window starts with an equal split; drag the divider to resize. Focus the divider and use Left/Right, or Home to restore equal panes.
- **Ctrl+F** searches the document and source. Enter moves to the next match; Shift+Enter moves back; Escape closes search.
- Use the zoom buttons, **Ctrl++**, **Ctrl+-**, or **Ctrl+0** to adjust reading size or restore 100%.
Contents and Source are hidden in new windows by default. Set independent defaults in **Settings → General**; toolbar toggles affect the current window.
## Choose your appearance
The document palette and application light/dark appearance are separate choices. Use **Theme** in the toolbar to select a document palette. Use the View menu for app appearance and Markdown extension/frontmatter options.
**File → Settings** provides these pages:
| Page | Controls |
| --- | --- |
| General | Reading width and custom CSS, Contents presentation, new-window pane defaults, and default file types |
| Document themes | Palette selection, editing, management, and JSON import/export |
| Editors | Named external editors, order, and document/folder launch preferences |
| Printing | Saved print defaults |
| Advanced | Startup splash, splash duration, and startup update checks |
Immediate settings save as you change them. Custom CSS uses **Apply**; theme/editor drafts use **Save** or **Cancel**. The Printing page provides explicit save/restore actions. Theme editing is intended for your own trusted preferences.
## Follow file changes
Saving a file in another app refreshes both the rendered document and source automatically, while retaining your relative reading position. Replacement saves are supported.
If a file is deleted or cannot be read, the last loaded document stays visible. The footer indicates a stale snapshot or failed reload. Restore the file or choose **File → Reload** to retry. The footer also shows the loaded snapshot's modification time and **Open folder** for Explorer.
## Print or save a PDF
1. Select **Print** in the toolbar, **File → Print**, or **Ctrl+P**.
2. Choose print theme, image/background options, font scaling, width, borders, and page padding.
3. Select **Print...** to hand the document to the system print dialog, or **Save PDF...** and choose a destination.
4. For PDF output, enable the optional actions to open the PDF or its containing folder.
Cancelling leaves the reading view unchanged. Use **Settings → Printing** to save defaults for later jobs.
## Make it your default Markdown reader
This requires an installed edition; a portable copy does not register default-file choices.
1. Open **File → Settings → General → Choose default file types**.
2. In Windows Default Apps, select MarkdownViewr and assign the extensions you want.
3. Try opening a Markdown file from Explorer.
Supported choices are `.md`, `.markdown`, `.mdown`, and `.mkd`; `.txt` is optional. Depending on Windows version, the button opens this app's page or the general Default Apps page. You can also right-click a file, choose **Open with**, and select the installed viewer.
The installer preserves your current defaults. Windows requires you to choose the replacement.
## Updates and local data
Installed stable and development editions update within their own channels using **Help → Check for updates**. They have separate settings, recent files, and logs. Portable copies require a fresh download.
Settings and logs are stored locally under `%LOCALAPPDATA%\markdownviewr-windows` or `%LOCALAPPDATA%\markdownviewr-windows-dev`. **Help → Activity log** opens the log viewer and provides a shortcut to its folder.
Documents are rendered locally. Authored scripts and remote images are blocked; local images must stay within the document folder. Following an external link is a deliberate action.
## Troubleshooting and support
- **The app cannot display a document:** check that the file still exists and is readable, then try **File → Reload**.
- **A remote image is absent:** remote image loading is blocked by the reader's document policy.
- **Open in editor is disabled:** hover for the reason, then configure an installed editor in Settings.
- **The viewer is not a default option:** use Setup rather than a portable copy, then reopen Windows Default Apps.
- **The app does not start:** confirm Microsoft Edge WebView2 Runtime is installed and inspect the activity logs if available.
For help, use [Discussions](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/discussions). Report reproducible problems through [Issues](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/issues/new/choose), including **Help → About** version, Windows version, and relevant sanitized logs. Report vulnerabilities privately using [SECURITY.md](../SECURITY.md).
@@ -0,0 +1,9 @@
/*
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
namespace MarkdownViewr.Windows.Core;
/// <summary>A verified dependency credit with fixed official project and license destinations, never document-supplied links.</summary>
public sealed record AboutComponent(string Name, string Version, string License, string ProjectUrl, string LicenseUrl);
@@ -0,0 +1,82 @@
/*
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
namespace MarkdownViewr.Windows.Core;
/// <summary>Dependency credits verified against restored assets/nuspec metadata; keep them current when package locks change.</summary>
public static class AboutComponents
{
/// <summary>Direct/transitive application dependencies, including the self-contained runtime and Microsoft SDK binary licenses.</summary>
public static IReadOnlyList<AboutComponent> Runtime { get; } = Array.AsReadOnly<AboutComponent>(
[
new("AngleSharp.Css", "1.0.2", "MIT", "https://github.com/AngleSharp/AngleSharp.Css", "https://licenses.nuget.org/MIT"),
new("AngleSharp", "1.7.2", "MIT", "https://github.com/AngleSharp/AngleSharp", "https://licenses.nuget.org/MIT"),
new("HtmlSanitizer", "9.2.1039", "MIT", "https://github.com/mganss/HtmlSanitizer", "https://licenses.nuget.org/MIT"),
new("KofTwentyTwo.AppKit.Updates", "0.1.0", "MIT", "https://github.com/KofTwentyTwo/AppKit", "https://licenses.nuget.org/MIT"),
new("KofTwentyTwo.AppKit.WinUI", "0.1.0", "MIT", "https://github.com/KofTwentyTwo/AppKit", "https://licenses.nuget.org/MIT"),
new("KofTwentyTwo.AppKit", "0.1.0", "MIT", "https://github.com/KofTwentyTwo/AppKit", "https://licenses.nuget.org/MIT"),
new("Markdig", "1.4.0", "BSD-2-Clause", "https://github.com/xoofx/markdig", "https://licenses.nuget.org/BSD-2-Clause"),
new("Microsoft.Web.WebView2", "1.0.3719.77", "Microsoft package license", "https://aka.ms/webview", "https://www.nuget.org/packages/Microsoft.Web.WebView2/1.0.3719.77/License"),
new("Microsoft.Windows.AI.MachineLearning", "2.1.74", "Microsoft package license", "https://github.com/microsoft/windowsappsdk", "https://www.nuget.org/packages/Microsoft.Windows.AI.MachineLearning/2.1.74/License"),
new("Microsoft.WindowsAppSDK.AI", "2.5.5", "Microsoft package license", "https://github.com/microsoft/windowsappsdk", "https://www.nuget.org/packages/Microsoft.WindowsAppSDK.AI/2.5.5/License"),
new("Microsoft.WindowsAppSDK.Base", "2.0.4", "Microsoft package license", "https://github.com/microsoft/windowsappsdk", "https://www.nuget.org/packages/Microsoft.WindowsAppSDK.Base/2.0.4/License"),
new("Microsoft.WindowsAppSDK.DWrite", "2.1.0", "Microsoft package license", "https://aka.ms/WindowsAppSDK", "https://www.nuget.org/packages/Microsoft.WindowsAppSDK.DWrite/2.1.0/License"),
new("Microsoft.WindowsAppSDK.Foundation", "2.3.12", "Microsoft package license", "https://aka.ms/windowsappsdk", "https://www.nuget.org/packages/Microsoft.WindowsAppSDK.Foundation/2.3.12/License"),
new("Microsoft.WindowsAppSDK.InteractiveExperiences", "2.1.9", "Microsoft package license", "https://aka.ms/windowsappsdk", "https://www.nuget.org/packages/Microsoft.WindowsAppSDK.InteractiveExperiences/2.1.9/License"),
new("Microsoft.WindowsAppSDK.ML", "2.1.94", "Microsoft package license", "https://github.com/microsoft/windowsml", "https://www.nuget.org/packages/Microsoft.WindowsAppSDK.ML/2.1.94/License"),
new("Microsoft.WindowsAppSDK.Runtime", "2.5.1", "Microsoft package license", "https://github.com/microsoft/windowsappsdk", "https://www.nuget.org/packages/Microsoft.WindowsAppSDK.Runtime/2.5.1/License"),
new("Microsoft.WindowsAppSDK.Search", "2.5.5", "Microsoft package license", "https://github.com/microsoft/windowsappsdk", "https://www.nuget.org/packages/Microsoft.WindowsAppSDK.Search/2.5.5/License"),
new("Microsoft.WindowsAppSDK.Widgets", "2.0.5", "Microsoft package license", "https://github.com/microsoft/windowsappsdk", "https://www.nuget.org/packages/Microsoft.WindowsAppSDK.Widgets/2.0.5/License"),
new("Microsoft.WindowsAppSDK.WinUI", "2.3.9", "Microsoft package license", "https://github.com/microsoft/windowsappsdk", "https://www.nuget.org/packages/Microsoft.WindowsAppSDK.WinUI/2.3.9/License"),
new("Microsoft.WindowsAppSDK", "2.5.1", "Microsoft package license", "https://github.com/microsoft/windowsappsdk", "https://www.nuget.org/packages/Microsoft.WindowsAppSDK/2.5.1/License"),
new("System.Numerics.Tensors", "9.0.0", "MIT", "https://github.com/dotnet/runtime", "https://licenses.nuget.org/MIT"),
new("Velopack", "1.2.161", "MIT", "https://github.com/velopack/velopack", "https://licenses.nuget.org/MIT"),
new(".NET Runtime", "10", "MIT and third-party notices", "https://github.com/dotnet/runtime", "https://github.com/dotnet/runtime/blob/main/LICENSE.TXT"),
]);
/// <summary>User-facing acknowledgements include shipped open-source libraries; the full inventory remains available for license audits.</summary>
public static IReadOnlyList<AboutComponent> OpenSource { get; } = Array.AsReadOnly(Runtime
.Where(component => !component.License.Contains("Microsoft package license", StringComparison.Ordinal))
.Append(new AboutComponent("WinUI 3", "2.3.9 (package)", "MIT (open-source project)", "https://github.com/microsoft/microsoft-ui-xaml", "https://github.com/microsoft/microsoft-ui-xaml/blob/main/LICENSE"))
.ToArray());
/// <summary>Build, analysis and test dependencies remain in the audit inventory rather than the user-facing About dialog.</summary>
public static IReadOnlyList<AboutComponent> Development { get; } = Array.AsReadOnly<AboutComponent>(
[
new("Meziantou.Analyzer", "3.0.296", "MIT", "https://github.com/meziantou/Meziantou.Analyzer", "https://licenses.nuget.org/MIT"),
new("Microsoft.Windows.SDK.BuildTools.MSIX", "1.7.251221100", "Microsoft package license", "https://aka.ms/WinSDKProjectURL", "https://www.nuget.org/packages/Microsoft.Windows.SDK.BuildTools.MSIX/1.7.251221100/License"),
new("Microsoft.Windows.SDK.BuildTools", "10.0.28000.2705", "Package license", "https://aka.ms/WinSDKProjectURL", "https://www.nuget.org/packages/Microsoft.Windows.SDK.BuildTools/10.0.28000.2705/License"),
new("Microsoft.CodeCoverage", "18.10.1", "MIT", "https://github.com/microsoft/vstest", "https://licenses.nuget.org/MIT"),
new("Microsoft.NET.Test.Sdk", "18.10.1", "MIT", "https://github.com/microsoft/vstest", "https://licenses.nuget.org/MIT"),
new("Microsoft.TestPlatform.ObjectModel", "18.10.1", "MIT", "https://github.com/microsoft/vstest", "https://licenses.nuget.org/MIT"),
new("Microsoft.TestPlatform.TestHost", "18.10.1", "MIT", "https://github.com/microsoft/vstest", "https://licenses.nuget.org/MIT"),
new("coverlet.collector", "10.1.0", "MIT", "https://github.com/coverlet-coverage/coverlet", "https://licenses.nuget.org/MIT"),
new("xunit.abstractions", "2.0.3", "Package license", "https://github.com/xunit/xunit", "https://www.nuget.org/packages/xunit.abstractions/2.0.3/License"),
new("xunit.analyzers", "1.18.0", "Apache-2.0", "https://github.com/xunit/xunit.analyzers", "https://licenses.nuget.org/Apache-2.0"),
new("xunit.assert", "2.9.3", "Apache-2.0", "https://github.com/xunit/xunit", "https://licenses.nuget.org/Apache-2.0"),
new("xunit.core", "2.9.3", "Apache-2.0", "https://github.com/xunit/xunit", "https://licenses.nuget.org/Apache-2.0"),
new("xunit.extensibility.core", "2.9.3", "Apache-2.0", "https://github.com/xunit/xunit", "https://licenses.nuget.org/Apache-2.0"),
new("xunit.extensibility.execution", "2.9.3", "Apache-2.0", "https://github.com/xunit/xunit", "https://licenses.nuget.org/Apache-2.0"),
new("xunit.runner.visualstudio", "4.0.0", "Apache-2.0", "https://github.com/xunit/visualstudio.xunit", "https://licenses.nuget.org/Apache-2.0"),
new("xunit", "2.9.3", "Apache-2.0", "https://github.com/xunit/xunit", "https://licenses.nuget.org/Apache-2.0"),
new("FlaUI.Core", "5.0.0", "MIT", "https://github.com/FlaUI/FlaUI", "https://www.nuget.org/packages/FlaUI.Core/5.0.0/License"),
new("FlaUI.UIA3", "5.0.0", "MIT", "https://github.com/FlaUI/FlaUI", "https://www.nuget.org/packages/FlaUI.UIA3/5.0.0/License"),
new("Interop.UIAutomationClient", "10.19041.0", "MIT", "https://github.com/Roemer/UIAutomation-Interop", "https://www.nuget.org/packages/Interop.UIAutomationClient/10.19041.0/License"),
new("Microsoft.Win32.SystemEvents", "8.0.0", "MIT", "https://github.com/dotnet/runtime", "https://licenses.nuget.org/MIT"),
new("System.CodeDom", "8.0.0", "MIT", "https://github.com/dotnet/runtime", "https://licenses.nuget.org/MIT"),
new("System.Configuration.ConfigurationManager", "8.0.1", "MIT", "https://github.com/dotnet/runtime", "https://licenses.nuget.org/MIT"),
new("System.Diagnostics.EventLog", "8.0.1", "MIT", "https://github.com/dotnet/runtime", "https://licenses.nuget.org/MIT"),
new("System.Diagnostics.PerformanceCounter", "8.0.1", "MIT", "https://github.com/dotnet/runtime", "https://licenses.nuget.org/MIT"),
new("System.Drawing.Common", "8.0.10", "MIT", "https://github.com/dotnet/winforms", "https://licenses.nuget.org/MIT"),
new("System.Management", "8.0.0", "MIT", "https://github.com/dotnet/runtime", "https://licenses.nuget.org/MIT"),
new("System.Security.Cryptography.ProtectedData", "8.0.0", "MIT", "https://github.com/dotnet/runtime", "https://licenses.nuget.org/MIT"),
new("System.Security.Permissions", "8.0.0", "MIT", "https://github.com/dotnet/runtime", "https://licenses.nuget.org/MIT"),
new("System.Windows.Extensions", "8.0.0", "MIT", "https://github.com/dotnet/runtime", "https://licenses.nuget.org/MIT"),
]);
}
+31 -11
View File
@@ -22,7 +22,7 @@ public static class AppIdentity
{
Id = "markdownviewr-windows",
DisplayName = "MarkdownViewr",
Tagline = "Built on the KofTwentyTwo Windows app template",
Tagline = "A Windows Markdown reader",
Description = "A Windows Markdown viewer with live reload, themes and document navigation.",
RepositoryUrl = new Uri("https://github.com/KofTwentyTwo/MarkdownViewr.Windows"),
Author = "James Maes",
@@ -30,28 +30,48 @@ public static class AppIdentity
// Keep the gradient in step with the mark (regenerate both with AppKit's BrandTool).
Brand = new AppBrand
{
GradientStart = "#0F766E",
GradientEnd = "#134E4A",
GradientStart = "#1A2B4D",
GradientEnd = "#0C1836",
IconSvgPath = "Assets/Brand/app-icon.svg",
IconIcoPath = "Assets/Brand/app.ico",
},
Attributions =
[
new("KofTwentyTwo.AppKit", "MIT License"),
new("CommunityToolkit.Mvvm", "MIT License"),
new("Velopack", "MIT License"),
new("Windows App SDK / WinUI 3", "MIT License, with Microsoft binary components"),
new(".NET Runtime", "MIT License"),
],
Attributions = AboutComponents.Runtime.Select(component => new Attribution(component.Name, component.License)).ToArray(),
};
/// <summary>Installed package channels select a separate development identity; loose builds retain the stable identity.</summary>
public static AppInfo ForChannel(string? channel) => string.Equals(channel, "dev", StringComparison.Ordinal)
? Info with { Id = "markdownviewr-windows-dev", DisplayName = "MarkdownViewr (dev)" }
: Info;
/// <summary>Where the app keeps its per-user data; honors the data-folder override variable.</summary>
public static AppPaths Paths { get; } = new(Info);
/// <summary>Darin Kelkhoff's original macOS viewer, credited without changing Windows support destinations.</summary>
public static Uri OriginalRepositoryUrl { get; } = new("https://github.com/darinkelkhoff/markdownViewr");
/// <summary>The original author's website, verified against his GitHub profile.</summary>
public static Uri OriginalAuthorWebsite { get; } = new("https://kelkhoff.com/");
/// <summary>The app's bug/feature templates, independent of any upstream project credited in About.</summary>
public static Uri IssuesUrl { get; } = new(Info.RepositoryUrl!.AbsoluteUri.TrimEnd('/') + "/issues/new/choose");
/// <summary>Community questions and tips in the app's own repository.</summary>
public static Uri SupportUrl { get; } = new(Info.RepositoryUrl!.AbsoluteUri.TrimEnd('/') + "/discussions");
/// <summary>Loads and saves <see cref="AppSettings"/> at <see cref="AppPaths.SettingsFile"/>.</summary>
public static SettingsStore<AppSettings> SettingsStore { get; } = new(Paths.SettingsFile, AppSettingsContext.Default.AppSettings);
+190 -9
View File
@@ -3,27 +3,208 @@
* SPDX-License-Identifier: MIT
*/
using System.Text.Json;
using System.Text.Json.Serialization;
using KofTwentyTwo.AppKit.Settings;
namespace MarkdownViewr.Windows.Core;
/// <summary>
/// The app's preferences: AppKit's shell settings (theme, splash, update checks) plus the
/// app's own. Add properties here, repair them in <see cref="Sanitize"/>, and they are
/// saved by <see cref="AppIdentity.SettingsStore"/>.
/// </summary>
/// <summary>Persistent AppKit shell and Markdown reading preferences.</summary>
public sealed class AppSettings : ShellSettings
{
/// <summary>The name last greeted, offered again at the next start.</summary>
public string LastName { get; set; } = "";
/// <summary>The selected document theme; independent of the Windows shell theme.</summary>
public string DocumentTheme { get; set; } = "Catppuccin Mocha";
/// <summary>Validated JSON themes, saved atomically with the AppKit preferences.</summary>
public string[] UserThemes { get; set; } = [];
/// <summary>Repairs the shell settings, then restores a missing name (JSON can supply null).</summary>
/// <summary>Visible theme order, including disabled choices managed separately from their JSON data.</summary>
public string[] ThemeOrder { get; set; } = [];
/// <summary>Disabled choices remain editable but are omitted from reading menus and cycling.</summary>
public string[] DisabledThemes { get; set; } = [];
/// <summary>Hidden bundled themes can be restored without damaging shipped resources.</summary>
public string[] DeletedBuiltInThemes { get; set; } = [];
/// <summary>The frontmatter display mode, saved independently of document contents.</summary>
public string Frontmatter { get; set; } = "Hide";
/// <summary>Repairs unknown saved mode values to the reference's default.</summary>
[JsonIgnore]
public Documents.FrontmatterMode FrontmatterKind => Enum.TryParse(Frontmatter, out Documents.FrontmatterMode mode) && Enum.IsDefined(mode) ? mode : Documents.FrontmatterMode.Hide;
/// <summary>Renders double-equals highlights; disabled syntax remains literal.</summary>
public bool RenderHighlight { get; set; } = true;
/// <summary>Renders single-caret superscripts independently of other Markdown extensions.</summary>
public bool RenderSuperscript { get; set; } = true;
/// <summary>Renders single-tilde subscripts while double-tilde GFM strikethrough remains enabled.</summary>
public bool RenderSubscript { get; set; } = true;
/// <summary>Renders double-plus inserted/underlined content.</summary>
public bool RenderUnderline { get; set; } = true;
/// <summary>An immutable snapshot captures this window's options before asynchronous parsing begins.</summary>
[JsonIgnore]
public Documents.MarkdownExtensions MarkdownExtensions => new(RenderHighlight, RenderSuperscript, RenderSubscript, RenderUnderline);
/// <summary>Limits reader content width, independently of print layout.</summary>
public bool ContentWidthEnabled { get; set; } = true;
/// <summary>The reference's 1000-pixel default scales with reading zoom.</summary>
public double ContentWidthPx { get; set; } = 1000;
/// <summary>Native user CSS shared across themes; saved separately from each theme's own CSS.</summary>
public string GlobalCss { get; set; } = "";
/// <summary>An immutable window snapshot keeps asynchronous style changes independent of saved theme data.</summary>
[JsonIgnore]
public Themes.ReadingAppearance ReadingAppearance => new(ContentWidthEnabled, ContentWidthPx, GlobalCss);
/// <summary>New document windows start with Contents hidden unless explicitly enabled in Settings.</summary>
public bool ShowOutlineByDefault { get; set; }
/// <summary>New document windows start with Source hidden unless explicitly enabled in Settings.</summary>
public bool ShowSourceByDefault { get; set; }
/// <summary>Shows the native heading sidebar in this window without changing the selected startup default.</summary>
public bool ShowOutline { get; set; }
/// <summary>Maximum heading depth displayed in the sidebar.</summary>
public int OutlineDepth { get; set; } = 3;
/// <summary>Long Contents entries wrap in full; otherwise they use one line with an ellipsis and full-title tooltip.</summary>
public bool WrapOutlineEntries { get; set; }
/// <summary>Optional native depth markers distinguish heading levels without changing titles or identities.</summary>
public bool ShowOutlineDepthMarkers { get; set; }
/// <summary>Last native sidebar width in device-independent pixels.</summary>
public double OutlineWidth { get; set; } = 220;
/// <summary>Shows the literal, read-only Markdown snapshot beside the rendered document.</summary>
public bool ShowSource { get; set; }
/// <summary>Legacy fixed-width preference retained for older settings; current source opens at50/50 and resizes within each window.</summary>
public double SourceWidth { get; set; } = 320;
/// <summary>Legacy single-editor path, migrated into the list and kept as the first choice for compatible settings readers.</summary>
public string ExternalEditor { get; set; } = "";
/// <summary>Ordered named editor choices; the legacy path migrates into one entry on load.</summary>
public Editors.EditorConfiguration[] ConfiguredEditors { get; set; } = [];
/// <summary>Private bounded successful-open history; clearable without deleting documents.</summary>
public string[] RecentFiles { get; set; } = [];
/// <summary>Saved print choices are independent of the reading theme and window-local zoom.</summary>
public Printing.PrintOptions Print { get; set; } = new();
/// <inheritdoc/>
public override void Sanitize()
{
base.Sanitize();
LastName ??= "";
Themes.ReadingAppearance appearance = ReadingAppearance.Sanitize();
ContentWidthPx = appearance.WidthPixels;
GlobalCss = appearance.GlobalCss;
RecentFiles = Documents.RecentDocuments.Sanitize(RecentFiles);
Print = (Print ?? new Printing.PrintOptions()).Sanitize();
ExternalEditor = Editors.EditorLaunch.IsExecutablePath(ExternalEditor) ? ExternalEditor : "";
ConfiguredEditors = Editors.EditorConfiguration.Sanitize(ConfiguredEditors);
if(ConfiguredEditors.Length == 0 && ExternalEditor.Length > 0)
{
ConfiguredEditors = [new Editors.EditorConfiguration(Guid.NewGuid(), "Editor", ExternalEditor)];
}
ExternalEditor = ConfiguredEditors.FirstOrDefault()?.Executable ?? "";
SourceWidth = double.IsFinite(SourceWidth) ? Math.Clamp(SourceWidth, 160, 600) : 320;
OutlineDepth = Math.Clamp(OutlineDepth, 1, 6);
OutlineWidth = double.IsFinite(OutlineWidth) ? Math.Clamp(OutlineWidth, 140, 500) : 220;
Frontmatter = FrontmatterKind.ToString();
if(string.IsNullOrWhiteSpace(DocumentTheme) || DocumentTheme.Length > 80 || DocumentTheme.Any(char.IsControl))
{
DocumentTheme = "Catppuccin Mocha";
}
UserThemes = (UserThemes ?? []).Where(IsValidTheme).ToArray();
ThemeOrder = Themes.ThemeManagement.SanitizeNames(ThemeOrder);
DisabledThemes = Themes.ThemeManagement.SanitizeNames(DisabledThemes);
DeletedBuiltInThemes = Themes.ThemeManagement.SanitizeNames(DeletedBuiltInThemes);
}
/// <summary>A corrupt saved theme cannot prevent application startup or damage the bundled catalog.</summary>
private static bool IsValidTheme(string json)
{
if(string.IsNullOrEmpty(json))
{
return false;
}
try
{
_ = Themes.ReaderTheme.ParseCompatible(json);
return true;
}
catch(JsonException)
{
return false;
}
}
}
@@ -11,6 +11,6 @@ namespace MarkdownViewr.Windows.Core;
/// <summary>Source-generated JSON serializer for <see cref="AppSettings"/>, so trimmed builds need no reflection.</summary>
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(AppSettings))]
internal sealed partial class AppSettingsContext : JsonSerializerContext
public sealed partial class AppSettingsContext : JsonSerializerContext
{
}
@@ -0,0 +1,91 @@
/*
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
namespace MarkdownViewr.Windows.Core.Documents;
/// <summary>Pairs literal source occurrences with rendered matches without counting the two views twice.</summary>
public sealed class DocumentFind
{
/// <summary>Bounds a native search term independently of document size.</summary>
public const int MaximumQueryCharacters = 256;
private int _renderedCount;
/// <summary>Finds nonoverlapping case-insensitive literal text; user input is never a regular expression.</summary>
public DocumentFind(string source, string query)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(query);
if(source.Length > MarkdownRenderer.MaximumCharacters || query.Length > MaximumQueryCharacters)
{
throw new ArgumentException("The document or find term exceeds its supported size.", source.Length > MarkdownRenderer.MaximumCharacters ? nameof(source) : nameof(query));
}
Query = query;
var matches = new List<int>();
if(query.Length > 0)
{
int offset = 0;
while(offset <= source.Length - query.Length)
{
int position = source.IndexOf(query, offset, StringComparison.OrdinalIgnoreCase);
if(position < 0)
{
break;
}
matches.Add(position);
offset = position + query.Length;
}
}
SourceMatches = matches.AsReadOnly();
Current = matches.Count > 0 ? 1 : 0;
}
/// <summary>The literal query shared by both surfaces.</summary>
public string Query { get; }
/// <summary>UTF-16 offsets into the literal source, in document order.</summary>
public IReadOnlyList<int> SourceMatches { get; }
/// <summary>Uses the reference's paired occurrence count, including text visible in only one surface.</summary>
public int Total => Math.Max(SourceMatches.Count, _renderedCount);
/// <summary>One-based current occurrence; zero means no matches.</summary>
public int Current { get; private set; }
/// <summary>The current source range, or no range when the rendered document has more occurrences.</summary>
public (int Start, int Length)? SourceSelection => Current > 0 && Current <= SourceMatches.Count ? (SourceMatches[Current - 1], Query.Length) : null;
/// <summary>Browser count changes retain a valid occurrence and cannot create a negative total.</summary>
public void SetRenderedCount(int count)
{
_renderedCount = Query.Length > 0 ? Math.Max(0, count) : 0;
Current = Total > 0 ? Math.Clamp(Current, 1, Total) : 0;
}
/// <summary>Advances one paired occurrence and wraps at the end.</summary>
public void Next() => Current = Total > 0 ? (Current >= Total ? 1 : Current + 1) : 0;
/// <summary>Moves back one paired occurrence and wraps at the beginning.</summary>
public void Previous() => Current = Total > 0 ? (Current <= 1 ? Total : Current - 1) : 0;
}
@@ -0,0 +1,80 @@
/*
* Copyright (c) 2026 Darin Kelkhoff
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
using System.Net;
using System.Text;
namespace MarkdownViewr.Windows.Core.Documents;
/// <summary>Adapts Darin's delimiter and line-based metadata display without interpreting YAML as commands.</summary>
internal static class DocumentFrontmatter
{
/// <summary>An absent closing delimiter leaves the original document untouched.</summary>
public static (string? Metadata, string Body) Split(string markdown)
{
string[] lines = markdown.Split('\n');
if(lines.Length < 2 || !string.Equals(lines[0].Trim(), "---", StringComparison.Ordinal))
{
return (null, markdown);
}
int end = Array.FindIndex(lines, 1, line => string.Equals(line.Trim(), "---", StringComparison.Ordinal));
if(end < 0)
{
return (null, markdown);
}
return (string.Join('\n', lines[1..end]), string.Join('\n', lines[(end + 1)..]));
}
/// <summary>Metadata is display text: split only at the first colon and escape every value before creating table markup.</summary>
public static string RenderMetadata(string metadata)
{
var html = new StringBuilder("<div class=\"frontmatter\"><table><caption>Document metadata</caption>");
foreach(string line in metadata.Split('\n'))
{
string text = line.Trim();
if(text.Length == 0)
{
continue;
}
int colon = text.IndexOf(':');
html.Append("<tr>");
if(colon >= 0)
{
html.Append("<th class=\"fm-key\">").Append(WebUtility.HtmlEncode(PlainValue(text[..colon]))).Append("</th>");
html.Append("<td class=\"fm-value\">").Append(WebUtility.HtmlEncode(PlainValue(text[(colon + 1)..]))).Append("</td>");
}
else
{
html.Append("<td class=\"fm-value\" colspan=\"2\">").Append(WebUtility.HtmlEncode(PlainValue(text))).Append("</td>");
}
html.Append("</tr>");
}
return html.Append("</table></div>").ToString();
}
/// <summary>Matches the reference's plain metadata labels while preserving mid-word punctuation.</summary>
private static string PlainValue(string text)
{
foreach(string marker in new[] { "***", "**", "__", "~~" })
{
text = text.Replace(marker, "", StringComparison.Ordinal);
}
text = text.Trim();
foreach(char marker in new[] { '*', '_', '`' })
{
if(text.StartsWith(marker) && text.EndsWith(marker))
{
text = text.Length > 1 ? text[1..^1] : "";
}
}
return text.Trim();
}
}
@@ -0,0 +1,9 @@
/*
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
namespace MarkdownViewr.Windows.Core.Documents;
/// <summary>A passive heading label and host-generated numeric identity for native navigation.</summary>
public sealed record DocumentHeading(int Index, int Level, string Title);
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
namespace MarkdownViewr.Windows.Core.Documents;
/// <summary>Serializes UI document requests, coalescing reloads while preserving a new picker selection.</summary>
public sealed class DocumentLoadScheduler
{
private bool _active;
private string? _pendingReloadPath;
private string? _pendingOpenPath;
/// <summary>Starts an idle loader; busy reloads coalesce and the latest explicit selection takes priority.</summary>
public bool TryBegin(string path, bool reload)
{
if(!_active)
{
_active = true;
return true;
}
if(reload)
{
_pendingReloadPath = path;
}
else
{
_pendingOpenPath = path;
}
return false;
}
/// <summary>Finishes one load and consumes at most one queued request for the current document.</summary>
public (string Path, bool Reload)? Complete(string? currentPath)
{
_active = false;
if(_pendingOpenPath is not null)
{
string path = _pendingOpenPath;
_pendingOpenPath = null;
return (path, false);
}
(string Path, bool Reload)? next = _pendingReloadPath is not null && currentPath is not null &&
string.Equals(_pendingReloadPath, currentPath, StringComparison.Ordinal) ? (currentPath, true) : null;
_pendingReloadPath = null;
return next;
}
}
@@ -0,0 +1,118 @@
/*
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
using System.Globalization;
using AngleSharp.Dom;
using AngleSharp.Html.Parser;
namespace MarkdownViewr.Windows.Core.Documents;
/// <summary>Builds native heading data and nested passive sections only after Markdown has been sanitized.</summary>
public sealed class DocumentOutline
{
private readonly List<DocumentHeading> _headings = [];
private readonly Dictionary<IElement, DocumentHeading> _elements = [];
/// <summary>Sanitizes first; authored identifiers cannot impersonate the generated navigation targets.</summary>
public DocumentOutline(string markdown, FrontmatterMode frontmatter = FrontmatterMode.Hide, MarkdownExtensions? extensions = null)
{
var sources = new DocumentSourceMap(markdown);
var sourceHeadings = new List<DocumentSourceHeading>();
using AngleSharp.Html.Dom.IHtmlDocument document = new HtmlParser().ParseDocument(new MarkdownRenderer(extensions).RenderWithSources(markdown, frontmatter, sources));
IElement body = document.Body!;
var fragments = new Dictionary<string, string>(StringComparer.Ordinal);
foreach(IElement element in body.QuerySelectorAll("h1,h2,h3,h4,h5,h6"))
{
var heading = new DocumentHeading(_headings.Count, element.LocalName[1] - '0', element.TextContent.Trim());
_headings.Add(heading);
if(element.GetAttribute(sources.AttributeName) is { } identity && sources.TryGetPosition(identity, out (int Start, int End) position))
{
sourceHeadings.Add(new DocumentSourceHeading(heading.Index, position.Start, position.End));
}
_elements.Add(element, heading);
element.Id = "mv-heading-" + heading.Index.ToString(CultureInfo.InvariantCulture);
string slug = Markdig.Helpers.LinkHelper.UrilizeAsGfm(heading.Title);
string fragment = slug;
for(int suffix = 1; fragments.ContainsKey(fragment); suffix++)
{
fragment = slug + "-" + suffix.ToString(CultureInfo.InvariantCulture);
}
fragments.Add(fragment, element.Id);
}
foreach(IElement element in body.QuerySelectorAll("[" + sources.AttributeName + "]"))
{
element.RemoveAttribute(sources.AttributeName);
}
foreach(IElement link in body.QuerySelectorAll("a[href]"))
{
string href = link.GetAttribute("href")!;
if(href.StartsWith('#') && fragments.TryGetValue(Uri.UnescapeDataString(href[1..]), out string? target))
{
link.SetAttribute("href", "#" + target);
}
}
foreach(IElement element in body.QuerySelectorAll("*").Reverse())
{
if(!_elements.ContainsKey(element))
{
WrapSections(element, document);
}
}
WrapSections(body, document);
Html = body.InnerHtml;
Headings = _headings.AsReadOnly();
SourceHeadings = sourceHeadings.AsReadOnly();
}
/// <summary>Sanitized HTML plus only the host's generated identifiers and structural attributes.</summary>
public string Html { get; }
/// <summary>Headings in document order, including duplicate labels and headings inside authored containers.</summary>
public IReadOnlyList<DocumentHeading> Headings { get; }
/// <summary>Parser-derived offsets of surviving headings in the original literal snapshot, independent of duplicate labels.</summary>
public IReadOnlyList<DocumentSourceHeading> SourceHeadings { get; }
/// <summary>Groups each heading until the next peer or ancestor, keeping nested section collapse independent.</summary>
private void WrapSections(IElement parent, IDocument document)
{
INode[] children = parent.ChildNodes.ToArray();
parent.TextContent = "";
var sections = new Stack<(int Level, IElement Content)>();
foreach(INode child in children)
{
if(child is IElement element && _elements.TryGetValue(element, out DocumentHeading? heading))
{
while(sections.TryPeek(out (int Level, IElement Content) top) && top.Level >= heading.Level)
{
sections.Pop();
}
IElement section = document.CreateElement("section");
IElement content = document.CreateElement("div");
content.SetAttribute("data-mv-body", heading.Index.ToString(CultureInfo.InvariantCulture));
section.AppendChild(element);
section.AppendChild(content);
(sections.Count > 0 ? sections.Peek().Content : parent).AppendChild(section);
sections.Push((heading.Level, content));
}
else
{
(sections.Count > 0 ? sections.Peek().Content : parent).AppendChild(child);
}
}
}
}
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2026 Darin Kelkhoff
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
using System.Net;
using MarkdownViewr.Windows.Core.Themes;
namespace MarkdownViewr.Windows.Core.Documents;
/// <summary>Preserves the reference rendered-document containers; TOC/source remain native Windows controls.</summary>
public static class DocumentPage
{
/// <summary>Wraps already-sanitized renderer output; title and theme CSS cannot break their HTML boundaries.</summary>
public static string Build(string sanitizedHtml, string title, ReaderTheme theme, ReadingAppearance appearance, double zoomScale)
{
ArgumentNullException.ThrowIfNull(sanitizedHtml);
ArgumentNullException.ThrowIfNull(title);
ArgumentNullException.ThrowIfNull(theme);
ArgumentNullException.ThrowIfNull(appearance);
return $"""<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>{WebUtility.HtmlEncode(title)}</title><style media="screen">{theme.GetReadingStyles(appearance, zoomScale)}</style></head><body><div id="content" role="main" aria-label="Document reading area" tabindex="0"><div id="content-inner">{sanitizedHtml}</div></div></body></html>""";
}
}
@@ -0,0 +1,99 @@
/*
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
namespace MarkdownViewr.Windows.Core.Documents;
/// <summary>Bounds passive image requests and user-initiated native document links to one opened document namespace.</summary>
public sealed class DocumentResourcePolicy
{
private static readonly HashSet<string> s_imageExtensions = new(StringComparer.OrdinalIgnoreCase)
{
".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".svg", ".avif", ".ico",
};
private static readonly HashSet<string> s_documentExtensions = new(StringComparer.OrdinalIgnoreCase)
{
".md", ".markdown", ".mdown", ".mkd", ".txt",
};
private static readonly HashSet<string> s_deviceNames = new(StringComparer.OrdinalIgnoreCase)
{
"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", "COM¹", "COM²", "COM³", "LPT¹", "LPT²", "LPT³",
};
private readonly string _prefix;
/// <summary>Creates an isolated logical resource namespace for this document; no HTTP server is started.</summary>
public DocumentResourcePolicy()
{
_prefix = $"/documents/{Guid.NewGuid():N}/";
DocumentUri = new Uri("https://viewer.markdownviewr.invalid" + _prefix + "index.html");
}
/// <summary>The only top-level document URI the browser host may serve.</summary>
public Uri DocumentUri { get; }
/// <summary>Allows only an in-range generated heading fragment on this exact document, without query/native arguments.</summary>
public int? GetHeadingIndex(Uri request, int headingCount)
{
ArgumentNullException.ThrowIfNull(request);
if(!request.IsAbsoluteUri || request.Query.Length != 0 || !string.Equals(request.GetLeftPart(UriPartial.Path), DocumentUri.AbsoluteUri, StringComparison.Ordinal))
{
return null;
}
return request.Fragment.StartsWith("#mv-heading-", StringComparison.Ordinal) &&
int.TryParse(request.Fragment.AsSpan(12), System.Globalization.NumberStyles.None, System.Globalization.CultureInfo.InvariantCulture, out int index) && index < headingCount ? index : null;
}
/// <summary>Returns a decoded relative image path, or denies the request before filesystem access.</summary>
public string? GetImageRelativePath(Uri request) => GetRelativePath(request, s_imageExtensions);
/// <summary>A deliberate local document link may read a supported type, never another scheme, query or native command.</summary>
public string? GetDocumentRelativePath(Uri request)
{
ArgumentNullException.ThrowIfNull(request);
return request.IsAbsoluteUri && request.Query.Length == 0 ? GetRelativePath(request, s_documentExtensions) : null;
}
/// <summary>Shares once-only decoding and segment containment between images and native document-link requests.</summary>
private string? GetRelativePath(Uri request, HashSet<string> extensions)
{
ArgumentNullException.ThrowIfNull(request);
if(!request.IsAbsoluteUri || !string.Equals(request.Scheme, "https", StringComparison.Ordinal) || !string.Equals(request.Host, DocumentUri.Host, StringComparison.Ordinal) || request.Port != 443 ||
request.UserInfo.Length != 0 || !request.AbsolutePath.StartsWith(_prefix, StringComparison.Ordinal))
{
return null;
}
string relative = Uri.UnescapeDataString(request.AbsolutePath[_prefix.Length..]);
if(!extensions.Contains(Path.GetExtension(relative)) || relative.Split('/').Any(segment => !IsSafeSegment(segment)))
{
return null;
}
return relative;
}
/// <summary>Rejects traversal, device names, separators, controls and Windows filename aliases.</summary>
private static bool IsSafeSegment(string segment)
{
return segment.Length > 0 && segment is not ("." or "..") && !segment.EndsWith(' ') && !segment.EndsWith('.') &&
!segment.Any(character => char.IsControl(character) || "<>:\\|?*".Contains(character, StringComparison.Ordinal)) &&
!s_deviceNames.Contains(segment.Split('.')[0]);
}
}
@@ -0,0 +1,136 @@
/*
* Copyright (c) 2026 Darin Kelkhoff
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
using System.Text.Json;
namespace MarkdownViewr.Windows.Core.Documents;
/// <summary>Validated numeric geometry from a fixed host query, used for bidirectional heading-anchored synchronization.</summary>
public sealed class DocumentScrollState
{
/// <summary>Stores only current generated identities, finite geometry and bounded collapse choices.</summary>
private DocumentScrollState(double position, double maximum, Dictionary<int, double> headings, List<int> collapsed)
{
Position = Math.Clamp(position, 0, maximum);
Maximum = maximum;
Headings = headings;
Collapsed = collapsed.AsReadOnly();
}
/// <summary>The actual vertical viewport offset, bounded by the available scroll range.</summary>
public double Position { get; }
/// <summary>The available vertical scroll range, independent of content height.</summary>
public double Maximum { get; }
/// <summary>Visible generated heading identities and document-coordinate tops.</summary>
public IReadOnlyDictionary<int, double> Headings { get; }
/// <summary>Actual hidden generated section bodies, including independent nested choices.</summary>
public IReadOnlyList<int> Collapsed { get; }
/// <summary>Rejects malformed, oversized, duplicate or out-of-range geometry rather than allowing arbitrary browser data into commands.</summary>
public static DocumentScrollState? FromBrowserResult(string? json, int headingCount)
{
ArgumentOutOfRangeException.ThrowIfNegative(headingCount);
if(json is null || json.Length > headingCount * 128L + 512)
{
return null;
}
try
{
using var document = JsonDocument.Parse(json);
JsonElement root = document.RootElement;
if(root.ValueKind != JsonValueKind.Array || root.GetArrayLength() != 4 || !Number(root[0], out double position) || !Number(root[1], out double maximum) || maximum < 0 ||
root[2].ValueKind != JsonValueKind.Array || root[2].GetArrayLength() > headingCount || root[3].ValueKind != JsonValueKind.Array || root[3].GetArrayLength() > headingCount)
{
return null;
}
var headings = new Dictionary<int, double>();
foreach(JsonElement entry in root[2].EnumerateArray())
{
if(entry.ValueKind != JsonValueKind.Array || entry.GetArrayLength() != 2 || !Identity(entry[0], headingCount, out int index) || !Number(entry[1], out double top) || !headings.TryAdd(index, top))
{
return null;
}
}
var collapsed = new List<int>();
var seen = new HashSet<int>();
foreach(JsonElement entry in root[3].EnumerateArray())
{
if(!Identity(entry, headingCount, out int index) || !seen.Add(index))
{
return null;
}
collapsed.Add(index);
}
return new DocumentScrollState(position, maximum, headings, collapsed);
}
catch(JsonException)
{
return null;
}
}
/// <summary>Interpolates paired heading coordinates with explicit top/bottom anchors; no-heading documents use relative scrolling.</summary>
public static double MapPosition(double position, double fromMaximum, double toMaximum, IEnumerable<(double From, double To)> anchors)
{
ArgumentNullException.ThrowIfNull(anchors);
if(!double.IsFinite(position) || !double.IsFinite(fromMaximum) || !double.IsFinite(toMaximum) || fromMaximum <= 0 || toMaximum <= 0)
{
return 0;
}
double current = Math.Clamp(position, 0, fromMaximum);
double previousFrom = 0;
double previousTo = 0;
foreach((double from, double to) in anchors.Where(pair => double.IsFinite(pair.From) && double.IsFinite(pair.To) && pair.From > 0 && pair.From < fromMaximum).OrderBy(pair => pair.From))
{
if(from <= previousFrom)
{
continue;
}
double target = Math.Clamp(to, 0, toMaximum);
if(current <= from)
{
return Math.Clamp(previousTo + (current - previousFrom) / (from - previousFrom) * (target - previousTo), 0, toMaximum);
}
previousFrom = from;
previousTo = target;
}
return Math.Clamp(previousTo + (current - previousFrom) / (fromMaximum - previousFrom) * (toMaximum - previousTo), 0, toMaximum);
}
/// <summary>Only finite JSON numbers participate in pixel interpolation.</summary>
private static bool Number(JsonElement element, out double value)
{
value = 0;
return element.ValueKind == JsonValueKind.Number && element.TryGetDouble(out value) && double.IsFinite(value);
}
/// <summary>Only the exact current generated heading range can be addressed.</summary>
private static bool Identity(JsonElement element, int count, out int value)
{
value = -1;
return element.ValueKind == JsonValueKind.Number && element.TryGetInt32(out value) && value >= 0 && value < count;
}
}
@@ -0,0 +1,9 @@
/*
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
namespace MarkdownViewr.Windows.Core.Documents;
/// <summary>Original UTF-16 heading syntax offsets, with an exclusive end, paired with a generated rendered identity.</summary>
public sealed record DocumentSourceHeading(int Index, int Start, int End);
@@ -0,0 +1,282 @@
/*
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
using System.Globalization;
using AngleSharp.Dom;
using AngleSharp.Html.Parser;
using Markdig;
using Markdig.Helpers;
using Markdig.Renderers.Html;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
namespace MarkdownViewr.Windows.Core.Documents;
/// <summary>Carries parser provenance through sanitation using a fresh private attribute removed before presentation.</summary>
internal sealed class DocumentSourceMap
{
private readonly int[] _lineEnds;
private readonly Dictionary<string, (int Start, int End)> _positions = new(StringComparer.Ordinal);
/// <summary>A document cannot predict the per-render attribute or provide its own source identities.</summary>
public DocumentSourceMap(string source)
{
ArgumentNullException.ThrowIfNull(source);
var ends = new List<int>();
for(int index = 0; index < source.Length; index++)
{
if(source[index] is '\r' or '\n')
{
ends.Add(index);
if(source[index] == '\r' && index + 1 < source.Length && source[index + 1] == '\n')
{
index++;
}
}
}
ends.Add(source.Length);
_lineEnds = ends.ToArray();
AttributeName = "data-mv-source-" + Guid.NewGuid().ToString("N");
}
/// <summary>Allowed only during this internal render, never in the public renderer's authored attribute policy.</summary>
public string AttributeName { get; }
/// <summary>Maps only surviving host-marked headings, not matching text labels or authored attributes.</summary>
public bool TryGetPosition(string identity, out (int Start, int End) position) => _positions.TryGetValue(identity, out position);
/// <summary>AST headings include Setext and exclude fences; raw HTML uses parser tokens and original line slices.</summary>
public string Render(string markdown, MarkdownPipeline pipeline, int offset, Func<MarkdownDocument, string> render)
{
MarkdownDocument document = Markdown.Parse(markdown, pipeline);
foreach(MarkdownObject node in document.Descendants())
{
if(node is HeadingBlock heading)
{
heading.GetAttributes().AddProperty(AttributeName, AddPosition(offset + heading.Span.Start, EndOfLine(offset + heading.Span.End)));
}
else if(node is HtmlBlock block)
{
AnnotateHtmlBlock(block, offset);
}
if(node is LeafBlock { Inline: not null } leaf)
{
AnnotateInlineHeadings(leaf, offset);
}
}
return render(document);
}
/// <summary>A real opening heading tag is distinguished from comments, closing tags and heading-like tag names.</summary>
private static bool IsHeadingTag(string tag) => tag.Length > 3 && tag.StartsWith("<h", StringComparison.OrdinalIgnoreCase) && tag[2] is >= '1' and <= '6' && (tag[3] is '>' or '/' || char.IsWhiteSpace(tag[3]));
/// <summary>Pairs actual inline heading nodes within one Markdown leaf, excluding fenced/inline code and comment lookalikes.</summary>
private void AnnotateInlineHeadings(LeafBlock leaf, int offset)
{
HtmlInline? opening = null;
foreach(HtmlInline inline in leaf.Inline!.Descendants().OfType<HtmlInline>())
{
if(IsHeadingTag(inline.Tag))
{
if(opening is not null)
{
AnnotateInlineHeading(opening, opening.Span.End, offset);
}
opening = inline;
}
else if(opening is not null && inline.Tag.Length > 4 && inline.Tag.StartsWith("</h", StringComparison.OrdinalIgnoreCase)
&& inline.Tag[3] is >= '1' and <= '6' && (inline.Tag[4] == '>' || char.IsWhiteSpace(inline.Tag[4])))
{
AnnotateInlineHeading(opening, inline.Span.End, offset);
opening = null;
}
}
if(opening is not null)
{
AnnotateInlineHeading(opening, InlineHeadingEnd(leaf, opening), offset);
}
}
/// <summary>Consecutive raw HTML blocks can finish an inline title; a new heading or Markdown block prevents borrowing later syntax.</summary>
private static int InlineHeadingEnd(LeafBlock leaf, HtmlInline opening)
{
ContainerBlock parent = leaf.Parent!;
for(int index = parent.IndexOf(leaf) + 1; index < parent.Count && parent[index] is HtmlBlock block; index++)
{
(int? end, bool boundary) = HeadingBoundary(block);
if(end is { } close)
{
return close;
}
if(boundary)
{
break;
}
}
return opening.Span.End;
}
/// <summary>Actual HTML tokens distinguish a finishing tag from a new heading, excluding comments and quoted-attribute lookalikes.</summary>
private static (int? End, bool Boundary) HeadingBoundary(HtmlBlock block)
{
StringLine[] lines = block.Lines.Lines;
string html = string.Join('\n', lines.Take(block.Lines.Count).Select(line => line.Slice.ToString()));
int? close = null;
bool seenTag = false;
var parser = new HtmlParser(new HtmlParserOptions
{
OnToken = (token, range) =>
{
if(!seenTag && token.Type is HtmlTokenType.StartTag or HtmlTokenType.EndTag
&& token.Name.Length == 2 && token.Name[0] == 'h' && token.Name[1] is >= '1' and <= '6')
{
seenTag = true;
if(token.Type == HtmlTokenType.EndTag)
{
close = range.End.Index - 1;
}
}
},
});
using AngleSharp.Html.Dom.IHtmlDocument document = parser.ParseDocument(html);
if(close is not { } position)
{
return (null, seenTag);
}
int index = 0;
while(position >= lines[index].Slice.Length)
{
position -= lines[index].Slice.Length + 1;
index++;
}
return (lines[index].Slice.Start + position, true);
}
/// <summary>Retains original syntax through the parsed closing line while inserting only the private identity in the opening tag.</summary>
private void AnnotateInlineHeading(HtmlInline opening, int end, int offset)
{
opening.Tag = opening.Tag.Insert(3, Attribute(AddPosition(offset + opening.Span.Start, EndOfLine(offset + end))));
}
/// <summary>Registers the parser-derived range under a generated identity; offsets stay in the immutable original snapshot.</summary>
private string AddPosition(int start, int end)
{
string identity = _positions.Count.ToString(CultureInfo.InvariantCulture);
_positions.Add(identity, (start, end));
return identity;
}
/// <summary>Raw heading markup stays on its original source line; the canonical snapshot is never rewritten.</summary>
private int EndOfLine(int start)
{
int index = Array.BinarySearch(_lineEnds, start);
return _lineEnds[index >= 0 ? index : ~index];
}
/// <summary>The private marker contains only generated ASCII identities, never document text.</summary>
private string Attribute(string identity) => " " + AttributeName + "=\"" + identity + "\"";
/// <summary>Tokenizer spans preserve complete raw headings across lines without mistaking quoted attributes or comments for closing syntax.</summary>
private static HtmlParser CreateSourceParser(Dictionary<int, int> headingEnds)
{
int pendingHeading = -1;
return new HtmlParser(new HtmlParserOptions
{
IsKeepingSourceReferences = true,
OnToken = (token, range) =>
{
if(token.Name.Length == 2 && token.Name[0] == 'h' && token.Name[1] is >= '1' and <= '6')
{
if(token.Type == HtmlTokenType.StartTag)
{
pendingHeading = range.Start.Index;
headingEnds.Add(pendingHeading, range.End.Index - 1);
}
else if(token.Type == HtmlTokenType.EndTag && pendingHeading >= 0)
{
headingEnds[pendingHeading] = range.End.Index - 1;
pendingHeading = -1;
}
}
},
});
}
/// <summary>Source slices retain quote/list indentation offsets while emitted raw HTML omits those Markdown prefixes.</summary>
private void AnnotateHtmlBlock(HtmlBlock block, int offset)
{
StringLine[] lines = block.Lines.Lines;
string html = string.Join('\n', lines.Take(block.Lines.Count).Select(line => line.Slice.ToString()));
var headingEnds = new Dictionary<int, int>();
using AngleSharp.Html.Dom.IHtmlDocument document = CreateSourceParser(headingEnds).ParseDocument(html);
int[] lineStarts = new int[block.Lines.Count];
int length = 0;
for(int index = 0; index < lineStarts.Length; index++)
{
lineStarts[index] = length;
length += lines[index].Slice.Length + 1;
}
var inserts = new Dictionary<int, List<(int Position, string Attribute)>>();
foreach(IElement heading in document.QuerySelectorAll("h1,h2,h3,h4,h5,h6"))
{
int position = heading.SourceReference!.Position.Index;
int index = Array.BinarySearch(lineStarts, position);
if(index < 0)
{
index = ~index - 1;
}
int column = position - lineStarts[index];
int start = offset + lines[index].Slice.Start + column;
if(!inserts.TryGetValue(index, out List<(int Position, string Attribute)>? changes))
{
changes = [];
inserts.Add(index, changes);
}
int endPosition = headingEnds[position];
int endIndex = Array.BinarySearch(lineStarts, endPosition);
endIndex = endIndex >= 0 ? endIndex : ~endIndex - 1;
int end = offset + lines[endIndex].Slice.Start + endPosition - lineStarts[endIndex];
changes.Add((column + 3, Attribute(AddPosition(start, EndOfLine(end)))));
}
foreach((int index, List<(int Position, string Attribute)> changes) in inserts)
{
string line = lines[index].Slice.ToString();
foreach((int position, string attribute) in changes.OrderByDescending(change => change.Position))
{
line = line.Insert(position, attribute);
}
lines[index].Slice = new StringSlice(line) { NewLine = lines[index].Slice.NewLine };
}
}
}
@@ -0,0 +1,247 @@
/*
* Copyright (c) 2026 Darin Kelkhoff
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
using System.Text;
namespace MarkdownViewr.Windows.Core.Documents;
/// <summary>Mirrors section folds in a literal native display while preserving the original snapshot and source identities.</summary>
public sealed class DocumentSourceText
{
private const string Ellipsis = "⋯\r";
private readonly string _source;
private readonly string _native;
private readonly int[] _crlfBreaks;
private readonly int[] _lineStarts;
private readonly DocumentSourceHeading[] _originalHeadings;
private readonly (int Index, int Start, int End)[] _sections;
private HashSet<int>? _collapsed;
private (int Start, int End)[] _ranges = [];
private int[] _rangeStarts = [];
private int[] _removed = [0];
private string _copyText = "";
private int[] _copyBreaks = [];
/// <summary>Indexes original/native line coordinates once and derives section boundaries from parser identities and heading levels.</summary>
public DocumentSourceText(string source, DocumentOutline outline)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(outline);
if(source.Length > MarkdownRenderer.MaximumCharacters)
{
throw new ArgumentException("The source exceeds the document size limit.", nameof(source));
}
if(outline.SourceHeadings.Any(heading => heading.Start < 0 || heading.End < heading.Start || heading.End > source.Length))
{
throw new ArgumentException("Source positions must belong to the committed snapshot.", nameof(outline));
}
_source = source;
_native = source.Replace("\r\n", "\r", StringComparison.Ordinal).Replace('\n', '\r');
var crlfBreaks = new List<int>();
var lineStarts = new List<int> { 0 };
for(int index = 0; index < source.Length; index++)
{
if(source[index] is '\r' or '\n')
{
if(source[index] == '\r' && index + 1 < source.Length && source[index + 1] == '\n')
{
index++;
crlfBreaks.Add(index);
}
lineStarts.Add(index + 1);
}
}
_crlfBreaks = crlfBreaks.ToArray();
_lineStarts = lineStarts.ToArray();
_originalHeadings = outline.SourceHeadings.OrderBy(heading => heading.Start).ToArray();
_sections = new (int, int, int)[_originalHeadings.Length];
var stack = new Stack<int>();
for(int index = 0; index < _originalHeadings.Length; index++)
{
DocumentSourceHeading heading = _originalHeadings[index];
int level = outline.Headings[heading.Index].Level;
while(stack.TryPeek(out int previous) && outline.Headings[_originalHeadings[previous].Index].Level >= level)
{
stack.Pop();
(int Index, int Start, int End) section = _sections[previous];
_sections[previous] = (section.Index, section.Start, LineStart(heading.Start));
}
_sections[index] = (heading.Index, AfterLineBreak(heading.End), source.Length);
stack.Push(index);
}
SetCollapsed([]);
}
/// <summary>Visible literal text with native CR newlines and a plain ellipsis for omitted bodies, never HTML or rich text.</summary>
public string Text { get; private set; } = "";
/// <summary>Only visible headings, with offsets into the actual displayed native string.</summary>
public IReadOnlyList<DocumentSourceHeading> Headings { get; private set; } = [];
/// <summary>Nested folds remain independent; overlapping parent/child bodies merge without losing saved choices.</summary>
public bool SetCollapsed(IEnumerable<int> collapsed)
{
ArgumentNullException.ThrowIfNull(collapsed);
var choices = collapsed.ToHashSet();
if(_collapsed is not null && _collapsed.SetEquals(choices))
{
return false;
}
_collapsed = choices;
var ranges = new List<(int Start, int End)>();
foreach((int index, int start, int end) in _sections.Where(section => choices.Contains(section.Index) && section.Start < section.End).OrderBy(section => section.Start))
{
if(ranges.Count > 0 && start <= ranges[^1].End)
{
ranges[^1] = (ranges[^1].Start, Math.Max(end, ranges[^1].End));
}
else
{
ranges.Add((start, end));
}
}
_ranges = ranges.Select(range => (ToNative(range.Start), ToNative(range.End))).ToArray();
_rangeStarts = _ranges.Select(range => range.Start).ToArray();
_removed = new int[_ranges.Length + 1];
var text = new StringBuilder();
int cursor = 0;
for(int index = 0; index < _ranges.Length; index++)
{
(int start, int end) = _ranges[index];
text.Append(_native, cursor, start - cursor).Append(Ellipsis);
_removed[index + 1] = _removed[index] + end - start - Ellipsis.Length;
cursor = end;
}
text.Append(_native, cursor, _native.Length - cursor);
Text = text.ToString();
BuildCopyText(ranges);
var headings = new List<DocumentSourceHeading>();
foreach(DocumentSourceHeading heading in _originalHeadings)
{
if(GetDisplayRange(heading.Start, heading.End - heading.Start) is { } range)
{
headings.Add(new DocumentSourceHeading(heading.Index, range.Start, range.Start + range.Length));
}
}
Headings = headings.AsReadOnly();
return true;
}
/// <summary>Maps original query spans into the visible native display; hidden or partly folded occurrences have no selection.</summary>
public (int Start, int Length)? GetDisplayRange(int start, int length)
{
ArgumentOutOfRangeException.ThrowIfNegative(start);
ArgumentOutOfRangeException.ThrowIfNegative(length);
if(start > _source.Length || length > _source.Length - start)
{
throw new ArgumentOutOfRangeException(nameof(length));
}
int nativeStart = ToNative(start);
int nativeEnd = ToNative(start + length);
int index = Array.BinarySearch(_rangeStarts, nativeStart);
index = index >= 0 ? index : ~index - 1;
if(index >= 0 && nativeStart < _ranges[index].End || index + 1 < _ranges.Length && nativeEnd > _ranges[index + 1].Start)
{
return null;
}
return (nativeStart - _removed[index + 1], nativeEnd - nativeStart);
}
/// <summary>Indexes copied visible original chunks separately from native paragraph coordinates.</summary>
private void BuildCopyText(List<(int Start, int End)> ranges)
{
var copy = new StringBuilder();
int cursor = 0;
foreach((int start, int end) in ranges)
{
copy.Append(_source, cursor, start - cursor).Append(Ellipsis);
cursor = end;
}
copy.Append(_source, cursor, _source.Length - cursor);
_copyText = copy.ToString();
var copyBreaks = new List<int>();
for(int index = 0; index + 1 < _copyText.Length; index++)
{
if(_copyText[index] == '\r' && _copyText[index + 1] == '\n')
{
copyBreaks.Add(index + 1 - copyBreaks.Count);
index++;
}
}
_copyBreaks = copyBreaks.ToArray();
}
/// <summary>Copies visible literal source with its original newline spelling; folded bodies remain omitted.</summary>
public string GetCopyText(int start, int length)
{
ArgumentOutOfRangeException.ThrowIfNegative(start);
ArgumentOutOfRangeException.ThrowIfNegative(length);
if(start > Text.Length || length > Text.Length - start)
{
throw new ArgumentOutOfRangeException(nameof(length));
}
int copyStart = ToCopy(start);
return _copyText.Substring(copyStart, ToCopy(start + length) - copyStart);
}
/// <summary>A native boundary after a CR includes the original LF of that CRLF, without altering lone line breaks.</summary>
private int ToCopy(int position)
{
int index = Array.BinarySearch(_copyBreaks, position);
return position + (index >= 0 ? index + 1 : ~index);
}
/// <summary>Each original CRLF loses its LF in WinUI text; lone CR/LF and Unicode retain their lengths.</summary>
private int ToNative(int position)
{
int index = Array.BinarySearch(_crlfBreaks, position);
return position - (index >= 0 ? index : ~index);
}
/// <summary>Preserves the complete next heading line, including quote/list prefixes.</summary>
private int LineStart(int position)
{
int index = Array.BinarySearch(_lineStarts, position);
return _lineStarts[index >= 0 ? index : ~index - 1];
}
/// <summary>A fold starts after the full ATX/Setext heading line and its original line break.</summary>
private int AfterLineBreak(int position)
{
if(position < _source.Length && _source[position] == '\r')
{
position++;
}
if(position < _source.Length && _source[position] == '\n')
{
position++;
}
return position;
}
}
@@ -0,0 +1,60 @@
/*
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
using System.Runtime.InteropServices;
using System.Text.Json;
namespace MarkdownViewr.Windows.Core.Documents;
/// <summary>A validated relative viewport that keeps the reading position when document height changes.</summary>
[StructLayout(LayoutKind.Auto)]
public readonly record struct DocumentViewport
{
/// <summary>Constructs a validated pair; arbitrary browser strings never become executable script.</summary>
private DocumentViewport(double x, double y)
{
HorizontalFraction = Math.Clamp(x, 0, 1);
VerticalFraction = Math.Clamp(y, 0, 1);
}
/// <summary>The horizontal position as a fraction of the available scroll range.</summary>
public double HorizontalFraction { get; }
/// <summary>The vertical position as a fraction of the available scroll range.</summary>
public double VerticalFraction { get; }
/// <summary>Reads the exact two-number result of the application's fixed viewport query.</summary>
public static DocumentViewport FromBrowserResult(string? json)
{
if(json is null || json.Length > 128)
{
return default;
}
try
{
using var document = JsonDocument.Parse(json);
JsonElement root = document.RootElement;
if(root.ValueKind == JsonValueKind.Array && root.GetArrayLength() == 2 &&
root[0].ValueKind == JsonValueKind.Number && root[1].ValueKind == JsonValueKind.Number &&
root[0].TryGetDouble(out double x) && root[1].TryGetDouble(out double y) &&
double.IsFinite(x) && double.IsFinite(y))
{
return new DocumentViewport(x, y);
}
}
catch(JsonException)
{
// An unavailable or malformed browser result falls back to the top of the document.
}
return default;
}
}
@@ -0,0 +1,19 @@
/*
* Copyright (c) 2026 Darin Kelkhoff
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
namespace MarkdownViewr.Windows.Core.Documents;
/// <summary>The reference app's three ways to display a leading frontmatter block.</summary>
public enum FrontmatterMode
{
/// <summary>Keep metadata out of the reading surface.</summary>
Hide,
/// <summary>Show escaped, line-based key/value rows.</summary>
Metadata,
/// <summary>Render the frontmatter block through the normal Markdown boundary.</summary>
Markdown,
}
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
using Markdig.Extensions.EmphasisExtras;
namespace MarkdownViewr.Windows.Core.Documents;
/// <summary>Independent reference Markdown extensions, enabled by default without altering the literal source.</summary>
/// <param name="Highlight">Renders double-equals text as a passive mark.</param>
/// <param name="Superscript">Renders single-caret text above the baseline.</param>
/// <param name="Subscript">Renders single-tilde text below the baseline, leaving double-tilde strikethrough available.</param>
/// <param name="Underline">Renders double-plus text as inserted/underlined content.</param>
public sealed record MarkdownExtensions(bool Highlight = true, bool Superscript = true, bool Subscript = true, bool Underline = true)
{
/// <summary>GFM strikethrough remains available regardless of the four independent extension preferences.</summary>
internal EmphasisExtraOptions EmphasisOptions
{
get
{
EmphasisExtraOptions options = EmphasisExtraOptions.Strikethrough;
if(Highlight)
{
options |= EmphasisExtraOptions.Marked;
}
if(Superscript)
{
options |= EmphasisExtraOptions.Superscript;
}
if(Subscript)
{
options |= EmphasisExtraOptions.Subscript;
}
if(Underline)
{
options |= EmphasisExtraOptions.Inserted;
}
return options;
}
}
}
@@ -0,0 +1,275 @@
/*
* Copyright (c) 2026 Darin Kelkhoff
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
using System.Text;
using System.Text.RegularExpressions;
using AngleSharp.Dom;
using AngleSharp.Html.Dom;
using AngleSharp.Html.Parser;
using Ganss.Xss;
using Markdig;
using Markdig.Extensions.Tables;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
namespace MarkdownViewr.Windows.Core.Documents;
/// <summary>Renders GitHub Flavored Markdown into passive HTML for the isolated document surface.</summary>
public sealed class MarkdownRenderer
{
/// <summary>Bounds parser work before an opened document can consume unbounded memory.</summary>
public const int MaximumCharacters = 16 * 1024 * 1024;
private readonly MarkdownPipeline _pipeline;
private readonly MarkdownExtensions _extensions;
private readonly HtmlSanitizer _sanitizer;
/// <summary>Allows document formatting without scripts, CSS, forms or native host capabilities.</summary>
public MarkdownRenderer(MarkdownExtensions? extensions = null)
{
_extensions = extensions ?? new MarkdownExtensions();
MarkdownPipelineBuilder pipeline = new MarkdownPipelineBuilder()
.UsePipeTables(new PipeTableOptions { UseGfmRules = true })
.UseTaskLists()
.UseEmphasisExtras(_extensions.EmphasisOptions)
.UseAutoLinks()
.UsePreciseSourceLocation();
pipeline.DocumentProcessed += PreserveLiteralExtensionBoundaries;
_pipeline = pipeline.Build();
_sanitizer = new HtmlSanitizer();
_sanitizer.AllowedTags.Clear();
_sanitizer.AllowedTags.UnionWith(["a", "abbr", "b", "blockquote", "br", "caption", "code", "dd", "del", "details", "div", "dl", "dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "img", "input", "ins", "kbd", "li", "mark", "ol", "p", "pre", "s", "small", "span", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]);
_sanitizer.AllowedAttributes.Clear();
_sanitizer.AllowedAttributes.UnionWith(["href", "src", "alt", "title", "align", "colspan", "rowspan", "width", "height", "type", "checked", "disabled", "class"]);
_sanitizer.AllowedSchemes.Clear();
_sanitizer.AllowedSchemes.UnionWith(["http", "https", "mailto"]);
_sanitizer.AllowedCssProperties.Clear();
_sanitizer.AllowedAtRules.Clear();
_sanitizer.PostProcessNode += MakeInputsPassive;
_sanitizer.PostProcessNode += FilterStylingClasses;
}
/// <summary>Preserves useful markup while sanitizing every parser result, including authored raw HTML.</summary>
public string Render(string markdown, FrontmatterMode frontmatter = FrontmatterMode.Hide) => Render(markdown, frontmatter, null);
/// <summary>Admits only the current private provenance attribute, then restores the ordinary authored-markup policy.</summary>
internal string RenderWithSources(string markdown, FrontmatterMode frontmatter, DocumentSourceMap sources)
{
_sanitizer.AllowedAttributes.Add(sources.AttributeName);
try
{
return Render(markdown, frontmatter, sources);
}
finally
{
_sanitizer.AllowedAttributes.Remove(sources.AttributeName);
}
}
/// <summary>Uses the same bounded parser and passive sanitation for public HTML and internal source-aware outlines.</summary>
private string Render(string markdown, FrontmatterMode frontmatter, DocumentSourceMap? sources)
{
ArgumentNullException.ThrowIfNull(markdown);
if(markdown.Length > MaximumCharacters)
{
throw new ArgumentException("The document exceeds the 16 Mi-character rendering limit.", nameof(markdown));
}
if(!Enum.IsDefined(frontmatter))
{
throw new ArgumentOutOfRangeException(nameof(frontmatter));
}
(string? metadata, string body) = DocumentFrontmatter.Split(markdown);
string prefix = !string.IsNullOrEmpty(metadata) ? frontmatter switch
{
FrontmatterMode.Metadata => RenderDocument(Markdown.Parse(DocumentFrontmatter.RenderMetadata(metadata), _pipeline), extendSubscript: false),
FrontmatterMode.Markdown => sources is null ? RenderDocument(Markdown.Parse("---\n" + metadata + "\n---", _pipeline)) : sources.Render("---\n" + metadata + "\n---", _pipeline, markdown.IndexOf('\n') + 1 - 4, RenderDocument),
_ => "",
} : "";
string html = sources is null ? RenderDocument(Markdown.Parse(body, _pipeline)) : sources.Render(body, _pipeline, markdown.Length - body.Length, RenderDocument);
return _sanitizer.Sanitize(prefix + html);
}
/// <summary>Normal documents use every selected extension; source annotation remains upstream of visible raw-text formatting.</summary>
private string RenderDocument(MarkdownDocument document) => RenderDocument(document, extendSubscript: true);
/// <summary>Formats visible authored HTML text without touching attributes/code; reference metadata excludes body-only subscript preprocessing.</summary>
private string RenderDocument(MarkdownDocument document, bool extendSubscript)
{
int codeDepth = 0;
foreach(MarkdownObject node in document.Descendants().Where(item => item is HtmlInline or HtmlBlock))
{
if(node is HtmlInline inline)
{
codeDepth = Math.Max(0, codeDepth + HtmlCodeBoundary(inline));
continue;
}
var block = (HtmlBlock)node;
string html = string.Join('\n', block.Lines.Lines.Take(block.Lines.Count).Select(line => line.Slice.ToString()));
var changes = new List<(int Start, int End, string Value)>();
var parser = new HtmlParser(new HtmlParserOptions
{
OnToken = (token, range) =>
{
if(token.Type is HtmlTokenType.StartTag or HtmlTokenType.EndTag && token.Name is "code" or "pre")
{
codeDepth = Math.Max(0, codeDepth + (token.Type == HtmlTokenType.StartTag ? 1 : -1));
}
else if(token.Type == HtmlTokenType.Character && codeDepth == 0)
{
string text = html[range.Start.Index..range.End.Index];
string formatted = FormatRawText(text, extendSubscript);
if(!string.Equals(text, formatted, StringComparison.Ordinal))
{
changes.Add((range.Start.Index, range.End.Index, formatted));
}
}
},
});
using IHtmlDocument parsed = parser.ParseDocument(html);
if(changes.Count == 0)
{
continue;
}
var rewritten = new StringBuilder(html);
for(int index = changes.Count - 1; index >= 0; index--)
{
(int start, int end, string value) = changes[index];
rewritten.Remove(start, end - start).Insert(start, value);
}
string[] lines = rewritten.ToString().Split('\n');
for(int index = 0; index < block.Lines.Count; index++)
{
block.Lines.Lines[index].Slice = new Markdig.Helpers.StringSlice(lines[index]);
}
}
return Markdown.ToHtml(document, _pipeline);
}
/// <summary>Reference extension syntax applies only to tokenized visible text, with bounded regex work and unchanged entity spelling.</summary>
private string FormatRawText(string text, bool extendSubscript)
{
foreach((bool enabled, string pattern, string tag) in new[]
{
(_extensions.Highlight, "==(.+?)==", "mark"),
(_extensions.Superscript, "\\^([^\\s\\^]+)\\^", "sup"),
(_extensions.Subscript && extendSubscript, "(?<!~)~([^\\s~]+)~(?!~)", "sub"),
(_extensions.Underline, "\\+\\+(.+?)\\+\\+", "ins"),
})
{
if(enabled)
{
text = Regex.Replace(text, pattern, "<" + tag + ">$1</" + tag + ">", RegexOptions.CultureInvariant, TimeSpan.FromSeconds(1));
}
}
return text;
}
/// <summary>Reference delimiters cannot span code/newlines; single caret/tilde pairs also cannot contain whitespace.</summary>
private static void PreserveLiteralExtensionBoundaries(MarkdownDocument document)
{
int codeDepth = 0;
foreach(MarkdownObject node in document.Descendants().Where(item => item is EmphasisInline or HtmlInline or HtmlBlock).ToArray())
{
if(node is HtmlInline html)
{
codeDepth = Math.Max(0, codeDepth + HtmlCodeBoundary(html));
}
else if(node is HtmlBlock block)
{
var parser = new HtmlParser(new HtmlParserOptions
{
OnToken = (token, _) =>
{
if(token.Type is HtmlTokenType.StartTag or HtmlTokenType.EndTag && token.Name is "code" or "pre")
{
codeDepth = Math.Max(0, codeDepth + (token.Type == HtmlTokenType.StartTag ? 1 : -1));
}
},
});
using IHtmlDocument htmlDocument = parser.ParseDocument(string.Join('\n', block.Lines.Lines.Take(block.Lines.Count).Select(line => line.Slice.ToString())));
}
if(node is not EmphasisInline emphasis || emphasis.DelimiterChar is not ('=' or '+' or '^' or '~') || (emphasis.DelimiterChar == '~' && emphasis.DelimiterCount == 2))
{
continue;
}
bool single = emphasis.DelimiterChar is '^' or '~';
if(codeDepth > 0 || emphasis.Descendants().Any(child => child is CodeInline or LineBreakInline || child is HtmlInline boundary && HtmlCodeBoundary(boundary) != 0 || (single && child is LiteralInline literal && literal.Content.ToString().Any(char.IsWhiteSpace))))
{
string delimiter = new(emphasis.DelimiterChar, emphasis.DelimiterCount);
Inline last = emphasis.ReplaceBy(new LiteralInline(delimiter));
last.InsertAfter(new LiteralInline(delimiter));
}
}
}
/// <summary>Only actual authored code/pre tag names protect extension delimiters, including mixed-case tags.</summary>
private static int HtmlCodeBoundary(HtmlInline html)
{
ReadOnlySpan<char> tag = html.Tag.AsSpan(1);
bool closing = tag.StartsWith("/", StringComparison.Ordinal);
if(closing)
{
tag = tag[1..];
}
tag = tag[..tag.IndexOfAny(" />\t\r\n\f".AsSpan())];
return tag.Equals("code", StringComparison.OrdinalIgnoreCase) || tag.Equals("pre", StringComparison.OrdinalIgnoreCase) ? closing ? -1 : 1 : 0;
}
/// <summary>Retains only passive reference styling classes; native identifiers, handlers and arbitrary document classes remain unavailable.</summary>
private static void FilterStylingClasses(object? sender, PostProcessNodeEventArgs args)
{
if(args.Node is IElement element && element.GetAttribute("class") is { } classes)
{
string[] allowed = classes.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).Where(value =>
value is "task-list" or "task-list-item" or "frontmatter" or "fm-key" or "fm-value" ||
value.StartsWith("language-", StringComparison.Ordinal) && value.Length is > 9 and <= 80 &&
value[9..].All(character => char.IsAsciiLetterOrDigit(character) || character is '_' or '-' or '+' or '#')).Distinct(StringComparer.Ordinal).ToArray();
if(allowed.Length == 0)
{
element.RemoveAttribute("class");
}
else
{
element.SetAttribute("class", string.Join(' ', allowed));
}
}
}
/// <summary>Task markers are display-only checkboxes; authored inputs cannot become active controls.</summary>
private static void MakeInputsPassive(object? sender, PostProcessNodeEventArgs args)
{
if(args.Node is IHtmlInputElement input)
{
input.Type = "checkbox";
input.IsDisabled = true;
}
}
}
@@ -0,0 +1,85 @@
/*
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
using Markdig;
using Markdig.Extensions.TaskLists;
using Markdig.Extensions.Yaml;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
namespace MarkdownViewr.Windows.Core.Documents;
/// <summary>Classifies literal UTF16 source spans with the existing parser; no token renders or executes document content.</summary>
public static class MarkdownSourceHighlighting
{
private static readonly MarkdownPipeline s_pipeline = new MarkdownPipelineBuilder().UsePipeTables().UseTaskLists().UseEmphasisExtras().UseAutoLinks().UseYamlFrontMatter().UsePreciseSourceLocation().Build();
/// <summary>Parser work is bounded by the supported document limit; cancellation discards stale classification during traversal.</summary>
public static IReadOnlyList<SourceToken> Parse(string source, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(source);
if(source.Length > MarkdownRenderer.MaximumCharacters)
{
throw new ArgumentException("The source exceeds the document size limit.", nameof(source));
}
cancellationToken.ThrowIfCancellationRequested();
MarkdownDocument document = Markdown.Parse(source, s_pipeline);
var tokens = new List<SourceToken>();
foreach(MarkdownObject node in document.Descendants())
{
cancellationToken.ThrowIfCancellationRequested();
SourceTokenKind? kind = Kind(node);
if(kind is { } value && node.Span.Start >= 0 && node.Span.End >= node.Span.Start && node.Span.End < source.Length)
{
int length = node.Span.End - node.Span.Start + 1;
if(node is ListItemBlock)
{
length = ListMarkerLength(source.AsSpan(node.Span.Start, length));
}
if(length > 0)
{
tokens.Add(new SourceToken(node.Span.Start, length, value));
}
}
}
return tokens.AsReadOnly();
}
/// <summary>Preorder parent/child spans permit nested markup to refine a block's color without changing its literal characters.</summary>
private static SourceTokenKind? Kind(MarkdownObject node) => node switch
{
YamlFrontMatterBlock => SourceTokenKind.Frontmatter,
HeadingBlock => SourceTokenKind.Heading,
CodeBlock or CodeInline => SourceTokenKind.Code,
EmphasisInline emphasis when emphasis.DelimiterChar == '~' && emphasis.DelimiterCount == 2 => SourceTokenKind.Strikethrough,
EmphasisInline emphasis when emphasis.DelimiterCount == 2 => SourceTokenKind.Strong,
EmphasisInline => SourceTokenKind.Emphasis,
LinkInline { IsImage: true } => SourceTokenKind.Image,
LinkInline or AutolinkInline => SourceTokenKind.Link,
ListItemBlock or TaskList => SourceTokenKind.List,
QuoteBlock => SourceTokenKind.Quote,
HtmlBlock or HtmlInline => SourceTokenKind.Html,
_ => null,
};
/// <summary>Only a list marker is colored; ordinary list prose remains readable and child code/emphasis keeps its own style.</summary>
private static int ListMarkerLength(ReadOnlySpan<char> span)
{
int length = 0;
while(length < span.Length && char.IsAsciiDigit(span[length]))
{
length++;
}
// Markdig's valid ListItemBlock span begins at its bullet or ordered marker.
return length + 1;
}
}
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
namespace MarkdownViewr.Windows.Core.Documents;
/// <summary>Keeps a bounded private history of successfully opened absolute Windows paths.</summary>
public static class RecentDocuments
{
/// <summary>Limits the menu and persisted history without probing files or disclosing paths in logs.</summary>
public const int MaximumCount = 20;
/// <summary>Repairs corrupt history and preserves most-recent order, using Windows path comparison.</summary>
public static string[] Sanitize(IEnumerable<string?>? paths)
=> (paths ?? []).Where(IsValid).Select(path => path!).Distinct(StringComparer.OrdinalIgnoreCase).Take(MaximumCount).ToArray();
/// <summary>Promotes only a path whose native document open has already succeeded.</summary>
public static string[] Record(IEnumerable<string?>? paths, string path)
=> Sanitize(new[] { path }.Concat(paths ?? []));
/// <summary>Saved history must not carry relative paths, URLs, control characters or unbounded strings.</summary>
private static bool IsValid(string? path)
=> !string.IsNullOrWhiteSpace(path) && path.Length <= 32767 && !path.Any(char.IsControl) && Path.IsPathFullyQualified(path);
}
@@ -0,0 +1,9 @@
/*
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
namespace MarkdownViewr.Windows.Core.Documents;
/// <summary>Offsets are UTF16 positions into the exact supplied text, including all whitespace and line breaks.</summary>
public sealed record SourceToken(int Start, int Length, SourceTokenKind Kind);
@@ -0,0 +1,22 @@
/*
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
namespace MarkdownViewr.Windows.Core.Documents;
/// <summary>Semantic source colors remain independent of HTML/CSS and native capabilities.</summary>
public enum SourceTokenKind
{
Heading,
Code,
Emphasis,
Strong,
Strikethrough,
Link,
Image,
List,
Quote,
Frontmatter,
Html,
}
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2026 Darin Kelkhoff
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
namespace MarkdownViewr.Windows.Core.Editors;
/// <summary>A stable named native editor choice; folder mode passes the document parent instead of the file.</summary>
public sealed record EditorConfiguration(Guid Id, string Name, string Executable, bool OpensFolder = false)
{
/// <summary>The maximum persisted and native editor command count.</summary>
public const int MaximumEditors = 32;
/// <summary>Stored choices cannot introduce empty identities, unbounded labels or executable command strings.</summary>
public static bool IsValid(EditorConfiguration? editor)
=> editor is not null && editor.Id != Guid.Empty && editor.Name is { Length: > 0 and <= 80 } &&
!string.IsNullOrWhiteSpace(editor.Name) && !editor.Name.Any(char.IsControl) && EditorLaunch.IsExecutablePath(editor.Executable);
/// <summary>Preserves valid order/identities and missing installed editors; bounds and repairs untrusted saved settings.</summary>
public static EditorConfiguration[] Sanitize(IEnumerable<EditorConfiguration?>? editors)
{
var seen = new HashSet<Guid>();
return (editors ?? []).Take(MaximumEditors).Where(editor => IsValid(editor) && seen.Add(editor!.Id)).Select(editor => editor! with { Name = editor.Name.Trim() }).ToArray();
}
}
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
using System.Diagnostics;
namespace MarkdownViewr.Windows.Core.Editors;
/// <summary>Builds an explicit editor process without interpreting a document path as a shell command.</summary>
public static class EditorLaunch
{
/// <summary>Only a bounded, fully qualified Windows executable can be persisted as the chosen editor.</summary>
public static bool IsExecutablePath(string? path)
=> path is { Length: > 0 and <= 32767 } && !path.Any(char.IsControl) && Path.IsPathFullyQualified(path) && string.Equals(Path.GetExtension(path), ".exe", StringComparison.OrdinalIgnoreCase);
/// <summary>Supplies exactly one literal file or parent-folder argument; availability is checked by the Windows adapter.</summary>
public static ProcessStartInfo Create(string executable, string document, bool opensFolder = false)
{
if(!IsExecutablePath(executable))
{
throw new ArgumentException("Choose an editor executable using its full path.", nameof(executable));
}
if(string.IsNullOrEmpty(document) || document.Length > 32767 || document.Any(char.IsControl) || !Path.IsPathFullyQualified(document) || Path.GetFileName(document).Length == 0)
{
throw new ArgumentException("The document must have a full file path.", nameof(document));
}
var start = new ProcessStartInfo
{
FileName = executable,
UseShellExecute = false,
WorkingDirectory = Path.GetDirectoryName(document),
};
start.ArgumentList.Add(opensFolder ? Path.GetDirectoryName(document)! : document);
return start;
}
}
@@ -1,60 +0,0 @@
/*
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
namespace MarkdownViewr.Windows.Core.Greetings;
/// <summary>
/// The template's example feature: a greeting that depends on the time of day. It shows
/// the shape every feature takes: UI-free logic in Core, the clock injected through
/// <see cref="TimeProvider"/> so tests are deterministic (K22-TEST-20), and input
/// validated where it enters (K22-CODE-22). Replace it with the app's real features.
/// </summary>
public sealed class GreetingService
{
/// <summary>Longest name accepted, so a pasted document cannot become a greeting.</summary>
public const int MaxNameLength = 100;
private readonly TimeProvider _time;
/// <summary>Creates the service over <paramref name="time"/>; the system clock when null.</summary>
public GreetingService(TimeProvider? time = null)
{
_time = time ?? TimeProvider.System;
}
/// <summary>
/// "Good morning, Ada!" (or afternoon, or evening, by local time). A blank name greets
/// "there".
/// </summary>
/// <exception cref="ArgumentException">The name is longer than <see cref="MaxNameLength"/>.</exception>
public string Greet(string? name)
{
string trimmed = name?.Trim() ?? "";
if(trimmed.Length > MaxNameLength)
{
throw new ArgumentException($"A name can be at most {MaxNameLength} characters.", nameof(name));
}
return $"{PartOfDay()}, {(trimmed.Length == 0 ? "there" : trimmed)}!";
}
/// <summary>Morning before noon, afternoon before six, evening after.</summary>
private string PartOfDay()
{
int hour = _time.GetLocalNow().Hour;
return hour switch
{
< 12 => "Good morning",
< 18 => "Good afternoon",
_ => "Good evening",
};
}
}
@@ -1,77 +0,0 @@
/*
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using KofTwentyTwo.AppKit.Logging;
using KofTwentyTwo.AppKit.Settings;
namespace MarkdownViewr.Windows.Core.Greetings;
/// <summary>
/// Presentation state for the greeting screen. It lives in Core, not in the WinUI project,
/// so it is tested headlessly; the window binds to it with x:Bind and holds no logic.
/// </summary>
public sealed partial class GreetingViewModel : ObservableObject
{
private readonly GreetingService _greetings;
private readonly AppSettings _settings;
private readonly SettingsStore<AppSettings> _store;
private readonly IActivityLog _log;
/// <summary>Creates the view model, starting from the name saved last time.</summary>
public GreetingViewModel(GreetingService greetings, AppSettings settings, SettingsStore<AppSettings> store, IActivityLog log)
{
ArgumentNullException.ThrowIfNull(greetings);
ArgumentNullException.ThrowIfNull(settings);
ArgumentNullException.ThrowIfNull(store);
ArgumentNullException.ThrowIfNull(log);
_greetings = greetings;
_settings = settings;
_store = store;
_log = log;
Name = settings.LastName;
}
/// <summary>The name typed by the user.</summary>
[ObservableProperty]
public partial string Name { get; set; } = "";
/// <summary>The greeting shown, or the reason the name was rejected.</summary>
[ObservableProperty]
public partial string Message { get; set; } = "";
/// <summary>Greets <see cref="Name"/>, remembers it for next time, and logs the event.</summary>
[RelayCommand]
private void Greet()
{
try
{
Message = _greetings.Greet(Name);
}
catch(ArgumentException ex)
{
Message = ex.Message;
_log.Warning($"Greeting rejected: {ex.Message}");
return;
}
_settings.LastName = Name.Trim();
if(!_store.Save(_settings))
{
_log.Warning("Could not save the last greeted name.");
}
_log.Info("Greeted the user.");
}
}
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
namespace MarkdownViewr.Windows.Core.Layout;
/// <summary>Allocates the space shared only by the document and source, preserving usable columns without a fixed maximum source width.</summary>
public static class SourcePaneLayout
{
/// <summary>A narrow window keeps the two panes equal; larger windows reserve160 source pixels and200 document pixels.</summary>
public static double Fraction(double available, double preferred = 0.5)
{
if(!double.IsFinite(available) || available <= 0)
{
return 0.5;
}
if(!double.IsFinite(preferred))
{
preferred = 0.5;
}
double minimum = Math.Min(160, available / 2) / available;
double maximum = 1 - Math.Min(200, available / 2) / available;
return Math.Clamp(preferred, minimum, maximum);
}
/// <summary>Dragging right reduces the source; invalid input cannot hide either reading surface.</summary>
public static double Resize(double available, double sourceWidth, double horizontalChange)
{
if(!double.IsFinite(available) || available <= 0 || !double.IsFinite(sourceWidth) || !double.IsFinite(horizontalChange))
{
return 0.5;
}
return Fraction(available, (sourceWidth - horizontalChange) / available);
}
}
@@ -11,7 +11,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" />
<PackageReference Include="KofTwentyTwo.AppKit" />
</ItemGroup>
@@ -19,4 +18,14 @@
<InternalsVisibleTo Include="MarkdownViewr.Windows.Core.Tests" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Markdig" />
<PackageReference Include="HtmlSanitizer" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Themes/BuiltIn/*.json" />
<EmbeddedResource Include="Themes/Document.css" LogicalName="MarkdownViewr.Windows.Core.Themes.Document.css" />
</ItemGroup>
</Project>
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2026 Darin Kelkhoff
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
namespace MarkdownViewr.Windows.Core.Navigation;
/// <summary>Bounded native reading commands adapted from the pinned reference's vim navigation.</summary>
public enum ReaderCommand
{
None,
Prefix,
LineDown,
LineUp,
HalfPageDown,
HalfPageUp,
PageUp,
Top,
Bottom,
NextHeading,
PreviousHeading,
FocusFind,
NextMatch,
PreviousMatch,
CloseFind,
ZoomIn,
ZoomOut,
ZoomReset,
Print,
NextTheme,
PreviousTheme,
}
@@ -0,0 +1,9 @@
/*
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
namespace MarkdownViewr.Windows.Core.Navigation;
/// <summary>A transient reading key and modifiers; uppercase and layout punctuation come from native translation.</summary>
public readonly record struct ReaderKey(char Character, bool Control = false, bool Shift = false, bool Alt = false, bool Windows = false, bool Repeat = false);
@@ -0,0 +1,103 @@
/*
* Copyright (c) 2026 Darin Kelkhoff
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
namespace MarkdownViewr.Windows.Core.Navigation;
/// <summary>Resolves the reference's vim sequences without interfering with text entry or modified native shortcuts.</summary>
public sealed class ReaderKeyboard
{
private char _prefix;
private long _prefixAt;
/// <summary>Uses injected monotonic milliseconds; a held prefix cannot imitate a deliberate two-key sequence.</summary>
public ReaderCommand Resolve(ReaderKey key, bool readingFocused, long milliseconds)
{
if(key.Alt || key.Windows)
{
Reset();
return ReaderCommand.None;
}
if(key.Control)
{
Reset();
return ResolveControl(key, readingFocused);
}
if(!readingFocused)
{
Reset();
return ReaderCommand.None;
}
long elapsed = milliseconds - _prefixAt;
if(_prefix != '\0' && key.Character == _prefix && !key.Repeat && elapsed is >= 0 and < 500)
{
char prefix = _prefix;
Reset();
return prefix switch { 'g' => ReaderCommand.Top, ']' => ReaderCommand.NextHeading, _ => ReaderCommand.PreviousHeading };
}
Reset();
if(key.Character is 'g' or ']' or '[')
{
_prefix = key.Character;
_prefixAt = milliseconds;
return ReaderCommand.Prefix;
}
return key.Character switch
{
'j' => ReaderCommand.LineDown,
'k' => ReaderCommand.LineUp,
'G' => ReaderCommand.Bottom,
'n' => ReaderCommand.NextMatch,
'N' => ReaderCommand.PreviousMatch,
'/' => ReaderCommand.FocusFind,
'\u001B' => ReaderCommand.CloseFind,
_ => ReaderCommand.None,
};
}
/// <summary>Windows find ignores Caps Lock; Shift-modified commands remain available to native controls.</summary>
private static ReaderCommand ResolveControl(ReaderKey key, bool readingFocused)
{
if(readingFocused && key.Character is '+' or '=' && (!key.Shift || key.Character == '+'))
{
return ReaderCommand.ZoomIn;
}
if(readingFocused && !key.Shift && key.Character is '-' or '0')
{
return key.Character == '-' ? ReaderCommand.ZoomOut : ReaderCommand.ZoomReset;
}
if(readingFocused && key.Shift && key.Character is '\u2193' or '\u2191')
{
return key.Character == '\u2193' ? ReaderCommand.NextTheme : ReaderCommand.PreviousTheme;
}
if(key.Shift)
{
return ReaderCommand.None;
}
if(readingFocused && char.ToUpperInvariant(key.Character) == 'P')
{
return ReaderCommand.Print;
}
if(char.ToUpperInvariant(key.Character) == 'F')
{
return ReaderCommand.FocusFind;
}
return readingFocused ? char.ToLowerInvariant(key.Character) switch
{
'd' => ReaderCommand.HalfPageDown,
'u' => ReaderCommand.HalfPageUp,
'b' => ReaderCommand.PageUp,
_ => ReaderCommand.None,
} : ReaderCommand.None;
}
/// <summary>Clears only the bounded prefix on focus, page or window changes; no arbitrary key history is stored.</summary>
public void Reset() => _prefix = '\0';
}
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2026 Darin Kelkhoff
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
namespace MarkdownViewr.Windows.Core.Navigation;
/// <summary>Keeps the pinned reference's bounded reading scale within one document window.</summary>
public sealed class ReaderZoom
{
/// <summary>Starts at actual size and never persists across independent windows.</summary>
public double Scale { get; private set; } = 1;
/// <summary>Applies only explicit zoom commands, retaining 30–500 percent bounds.</summary>
public void Apply(ReaderCommand command)
{
Scale = command switch
{
ReaderCommand.ZoomIn => Math.Min(Scale * 1.1, 5),
ReaderCommand.ZoomOut => Math.Max(Scale / 1.1, 0.3),
ReaderCommand.ZoomReset => 1,
_ => Scale,
};
}
}
@@ -0,0 +1,19 @@
/*
* Copyright (c) 2026 Darin Kelkhoff
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
namespace MarkdownViewr.Windows.Core.Printing;
/// <summary>Immutable print preferences adapted from the reference; cancellation retains the saved draft.</summary>
public sealed record PrintOptions(string Theme = "Clean Printing", bool Backgrounds = false, bool Images = true,
bool MatchScreenZoom = false, bool FullPage = true, bool Borders = false, double PaddingPixels = 0)
{
/// <summary>Repairs corrupt preferences without allowing arbitrary CSS values or unbounded padding.</summary>
public PrintOptions Sanitize() => this with
{
Theme = string.IsNullOrWhiteSpace(Theme) || Theme.Length > 80 || Theme.Any(char.IsControl) ? "Clean Printing" : Theme,
PaddingPixels = double.IsFinite(PaddingPixels) ? Math.Clamp(PaddingPixels, 0, 96) : 0,
};
}
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2026 Darin Kelkhoff
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
using System.Globalization;
using System.Text;
using MarkdownViewr.Windows.Core.Themes;
namespace MarkdownViewr.Windows.Core.Printing;
/// <summary>Generates print-only CSS; the native host keeps reading styles in a separate screen stylesheet.</summary>
public static class PrintStyles
{
private static readonly ReaderTheme s_clean = ReaderTheme.Parse("""
{"name":"Clean Printing","schemaVersion":1,
"colors":{"background":"#ffffff","text":"#24292e","heading1":"#1b1f23","heading2":"#1b1f23",
"heading3":"#1b1f23","heading4":"#1b1f23","heading5":"#1b1f23","heading6":"#1b1f23",
"link":"#0366d6","codeBackground":"#f6f8fa","codeText":"#24292e","blockquoteBorder":"#dfe2e5",
"blockquoteBackground":"#fafbfc","highlightBackground":"#fff5b1","highlightText":"#24292e"},
"fonts":{"body":"System","heading":"System","code":"SF Mono"},
"sizes":{"baseFontSize":14,"h1Size":28,"h2Size":23,"h3Size":20,"h4Size":18,"h5Size":16,"h6Size":15,
"codeFontSize":13,"lineHeight":1.7},"customCSS":""}
""");
/// <summary>Resolves the reference's special choices and current catalog without mutating any theme or document state.</summary>
public static string Build(PrintOptions options, ReaderTheme activeTheme, IEnumerable<ReaderTheme> themes, double readingZoom)
{
ArgumentNullException.ThrowIfNull(options);
ArgumentNullException.ThrowIfNull(activeTheme);
ArgumentNullException.ThrowIfNull(themes);
if(!double.IsFinite(readingZoom) || readingZoom is < 0.3 or > 5)
{
throw new ArgumentOutOfRangeException(nameof(readingZoom));
}
options = options.Sanitize();
ReaderTheme? theme = options.Theme switch
{
"Active Theme" => activeTheme,
"Plain HTML" => null,
"Clean Printing" => s_clean,
_ => themes.FirstOrDefault(candidate => string.Equals(candidate.Name, options.Theme, StringComparison.Ordinal)) ?? s_clean,
};
var css = new StringBuilder("#content-inner{max-width:900px;margin:0 auto;}" + (theme?.GetStyles(options.MatchScreenZoom ? readingZoom : 1) ?? ""));
css.Append("""
html,body,#content{height:auto!important;overflow:visible!important;display:block!important;}
#content{width:100%!important;max-width:none!important;margin:0!important;padding:0!important;}
#content-inner{padding:0!important;}
[data-mv-body][hidden]{display:block!important;}
*{-webkit-print-color-adjust:exact!important;print-color-adjust:exact!important;}
h1,h2,h3,h4,h5,h6{break-after:avoid!important;}
pre,blockquote,tr,li,img{break-inside:avoid!important;}
pre{white-space:pre-wrap!important;overflow:visible!important;}
img{max-width:100%!important;}thead{display:table-header-group!important;}
th,td{overflow-wrap:anywhere!important;white-space:normal!important;}
""");
css.Append("body{padding:").Append(options.PaddingPixels.ToString("R", CultureInfo.InvariantCulture)).Append("px!important;}");
if(options.FullPage)
{
css.Append("body{width:auto!important;max-width:none!important;margin:0!important;}#content-inner{width:auto!important;max-width:none!important;margin:0!important;}");
}
if(!options.Backgrounds)
{
css.Append("""
html,body{background:#ffffff!important;color:#000000!important;}
*{background:transparent!important;background-image:none!important;color:#000000!important;
-webkit-print-color-adjust:economy!important;print-color-adjust:economy!important;}
a{color:#0969da!important;text-decoration:underline!important;}
hr,h1,h2{border-bottom:1px solid #dddddd!important;}
th,td{border:1px solid #dddddd!important;}blockquote{border-left:3px solid #cccccc!important;}
""");
}
if(options.Borders)
{
css.Append("pre,blockquote,code,details{border:1px solid currentColor!important;}");
}
if(!options.Images)
{
css.Append("img{display:none!important;}");
}
return css.ToString();
}
}
@@ -0,0 +1,36 @@
/*
* Copyright (c) 2026 James Maes
* SPDX-License-Identifier: MIT
*/
using System.Text.Json;
using System.Text.Json.Nodes;
namespace MarkdownViewr.Windows.Core;
/// <summary>Merges only a document window's changed preferences so its older snapshot cannot overwrite sibling settings.</summary>
public static class SettingsChanges
{
/// <summary>Copies the source-generated settings contract without reflection or shared mutable arrays.</summary>
public static AppSettings Copy(AppSettings settings)
=> JsonSerializer.SerializeToNode(settings, AppSettingsContext.Default.AppSettings)!.Deserialize(AppSettingsContext.Default.AppSettings)!;
/// <summary>Preserves current store values for every property the window has not changed since its last successful save.</summary>
public static AppSettings Merge(AppSettings current, AppSettings before, AppSettings edited)
{
JsonObject result = JsonSerializer.SerializeToNode(current, AppSettingsContext.Default.AppSettings)!.AsObject();
JsonObject baseline = JsonSerializer.SerializeToNode(before, AppSettingsContext.Default.AppSettings)!.AsObject();
JsonObject changes = JsonSerializer.SerializeToNode(edited, AppSettingsContext.Default.AppSettings)!.AsObject();
foreach((string name, JsonNode? value) in changes)
{
if(!JsonNode.DeepEquals(value, baseline[name]))
{
result[name] = value?.DeepClone();
}
}
return result.Deserialize(AppSettingsContext.Default.AppSettings)!;
}
}
@@ -0,0 +1,37 @@
{
"name": "Catppuccin Latte",
"colors": {
"background": "#eff1f5",
"text": "#4c4f69",
"heading1": "#d20f39",
"heading2": "#1e66f5",
"heading3": "#8839ef",
"heading4": "#df8e1d",
"heading5": "#40a02b",
"heading6": "#179299",
"link": "#1e66f5",
"codeBackground": "#e6e9ef",
"codeText": "#40a02b",
"blockquoteBorder": "#8839ef",
"blockquoteBackground": "#e6e9ef",
"highlightBackground": "#f9e2af",
"highlightText": "#4c4f69"
},
"fonts": {
"body": "System",
"heading": "System",
"code": "SF Mono"
},
"sizes": {
"baseFontSize": 14,
"h1Size": 28,
"h2Size": 23,
"h3Size": 20,
"h4Size": 18,
"h5Size": 16,
"h6Size": 15,
"codeFontSize": 13,
"lineHeight": 1.7
},
"customCSS": ""
}
@@ -0,0 +1,37 @@
{
"name": "Catppuccin Mocha",
"colors": {
"background": "#1e1e2e",
"text": "#cdd6f4",
"heading1": "#cba6f7",
"heading2": "#89b4fa",
"heading3": "#89b4fa",
"heading4": "#74c7ec",
"heading5": "#74c7ec",
"heading6": "#74c7ec",
"link": "#89dceb",
"codeBackground": "#181825",
"codeText": "#a6e3a1",
"blockquoteBorder": "#cba6f7",
"blockquoteBackground": "#24243a",
"highlightBackground": "#f9e2af",
"highlightText": "#1e1e2e"
},
"fonts": {
"body": "System",
"heading": "System",
"code": "SF Mono"
},
"sizes": {
"baseFontSize": 14,
"h1Size": 28,
"h2Size": 23,
"h3Size": 20,
"h4Size": 18,
"h5Size": 16,
"h6Size": 15,
"codeFontSize": 13,
"lineHeight": 1.7
},
"customCSS": ""
}
@@ -0,0 +1,37 @@
{
"name": "Dracula",
"colors": {
"background": "#282a36",
"text": "#f8f8f2",
"heading1": "#bd93f9",
"heading2": "#8be9fd",
"heading3": "#8be9fd",
"heading4": "#ff79c6",
"heading5": "#ff79c6",
"heading6": "#ff79c6",
"link": "#8be9fd",
"codeBackground": "#1e1f29",
"codeText": "#50fa7b",
"blockquoteBorder": "#bd93f9",
"blockquoteBackground": "#2e3042",
"highlightBackground": "#f1fa8c",
"highlightText": "#282a36"
},
"fonts": {
"body": "System",
"heading": "System",
"code": "SF Mono"
},
"sizes": {
"baseFontSize": 14,
"h1Size": 28,
"h2Size": 23,
"h3Size": 20,
"h4Size": 18,
"h5Size": 16,
"h6Size": 15,
"codeFontSize": 13,
"lineHeight": 1.7
},
"customCSS": ""
}
@@ -0,0 +1,37 @@
{
"name": "Everforest Dark",
"colors": {
"background": "#2d353b",
"text": "#d3c6aa",
"heading1": "#e67e80",
"heading2": "#7fbbb3",
"heading3": "#d699b6",
"heading4": "#dbbc7f",
"heading5": "#a7c080",
"heading6": "#83c092",
"link": "#7fbbb3",
"codeBackground": "#272e33",
"codeText": "#a7c080",
"blockquoteBorder": "#d699b6",
"blockquoteBackground": "#343f44",
"highlightBackground": "#dbbc7f",
"highlightText": "#2d353b"
},
"fonts": {
"body": "System",
"heading": "System",
"code": "SF Mono"
},
"sizes": {
"baseFontSize": 14,
"h1Size": 28,
"h2Size": 23,
"h3Size": 20,
"h4Size": 18,
"h5Size": 16,
"h6Size": 15,
"codeFontSize": 13,
"lineHeight": 1.7
},
"customCSS": ""
}
@@ -0,0 +1,37 @@
{
"name": "GitHub Dark",
"colors": {
"background": "#0d1117",
"text": "#e6edf3",
"heading1": "#79c0ff",
"heading2": "#79c0ff",
"heading3": "#79c0ff",
"heading4": "#79c0ff",
"heading5": "#79c0ff",
"heading6": "#79c0ff",
"link": "#58a6ff",
"codeBackground": "#161b22",
"codeText": "#e6edf3",
"blockquoteBorder": "#3b434b",
"blockquoteBackground": "#141a22",
"highlightBackground": "#e3b341",
"highlightText": "#0d1117"
},
"fonts": {
"body": "System",
"heading": "System",
"code": "SF Mono"
},
"sizes": {
"baseFontSize": 14,
"h1Size": 28,
"h2Size": 23,
"h3Size": 20,
"h4Size": 18,
"h5Size": 16,
"h6Size": 15,
"codeFontSize": 13,
"lineHeight": 1.7
},
"customCSS": ""
}
@@ -0,0 +1,37 @@
{
"name": "GitHub Light",
"colors": {
"background": "#ffffff",
"text": "#1f2328",
"heading1": "#0550ae",
"heading2": "#0550ae",
"heading3": "#0550ae",
"heading4": "#0550ae",
"heading5": "#0550ae",
"heading6": "#0550ae",
"link": "#0969da",
"codeBackground": "#f6f8fa",
"codeText": "#1f2328",
"blockquoteBorder": "#d0d7de",
"blockquoteBackground": "#f6f8fa",
"highlightBackground": "#fff8c5",
"highlightText": "#1f2328"
},
"fonts": {
"body": "System",
"heading": "System",
"code": "SF Mono"
},
"sizes": {
"baseFontSize": 14,
"h1Size": 28,
"h2Size": 23,
"h3Size": 20,
"h4Size": 18,
"h5Size": 16,
"h6Size": 15,
"codeFontSize": 13,
"lineHeight": 1.7
},
"customCSS": ""
}
@@ -0,0 +1,37 @@
{
"name": "Gruvbox Dark",
"colors": {
"background": "#282828",
"text": "#ebdbb2",
"heading1": "#fb4934",
"heading2": "#83a598",
"heading3": "#b8bb26",
"heading4": "#fabd2f",
"heading5": "#d3869b",
"heading6": "#8ec07c",
"link": "#83a598",
"codeBackground": "#1d2021",
"codeText": "#b8bb26",
"blockquoteBorder": "#d65d0e",
"blockquoteBackground": "#32302f",
"highlightBackground": "#fabd2f",
"highlightText": "#282828"
},
"fonts": {
"body": "System",
"heading": "System",
"code": "SF Mono"
},
"sizes": {
"baseFontSize": 14,
"h1Size": 28,
"h2Size": 23,
"h3Size": 20,
"h4Size": 18,
"h5Size": 16,
"h6Size": 15,
"codeFontSize": 13,
"lineHeight": 1.7
},
"customCSS": ""
}
@@ -0,0 +1,37 @@
{
"name": "Gruvbox Light",
"colors": {
"background": "#fbf1c7",
"text": "#3c3836",
"heading1": "#cc241d",
"heading2": "#458588",
"heading3": "#98971a",
"heading4": "#d79921",
"heading5": "#b16286",
"heading6": "#689d6a",
"link": "#458588",
"codeBackground": "#f2e5bc",
"codeText": "#79740e",
"blockquoteBorder": "#d65d0e",
"blockquoteBackground": "#f2e5bc",
"highlightBackground": "#fabd2f",
"highlightText": "#282828"
},
"fonts": {
"body": "System",
"heading": "System",
"code": "SF Mono"
},
"sizes": {
"baseFontSize": 14,
"h1Size": 28,
"h2Size": 23,
"h3Size": 20,
"h4Size": 18,
"h5Size": 16,
"h6Size": 15,
"codeFontSize": 13,
"lineHeight": 1.7
},
"customCSS": ""
}
@@ -0,0 +1,37 @@
{
"name": "Kanagawa",
"colors": {
"background": "#1f1f28",
"text": "#dcd7ba",
"heading1": "#c34043",
"heading2": "#7e9cd8",
"heading3": "#957fb8",
"heading4": "#e6c384",
"heading5": "#76946a",
"heading6": "#6a9589",
"link": "#7fb4ca",
"codeBackground": "#16161d",
"codeText": "#98bb6c",
"blockquoteBorder": "#957fb8",
"blockquoteBackground": "#2a2a37",
"highlightBackground": "#ff9e3b",
"highlightText": "#1f1f28"
},
"fonts": {
"body": "System",
"heading": "System",
"code": "SF Mono"
},
"sizes": {
"baseFontSize": 14,
"h1Size": 28,
"h2Size": 23,
"h3Size": 20,
"h4Size": 18,
"h5Size": 16,
"h6Size": 15,
"codeFontSize": 13,
"lineHeight": 1.7
},
"customCSS": ""
}
@@ -0,0 +1,37 @@
{
"name": "Monokai",
"colors": {
"background": "#272822",
"text": "#f8f8f2",
"heading1": "#f92672",
"heading2": "#66d9ef",
"heading3": "#a6e22e",
"heading4": "#e6db74",
"heading5": "#fd971f",
"heading6": "#ae81ff",
"link": "#66d9ef",
"codeBackground": "#1e1f1c",
"codeText": "#a6e22e",
"blockquoteBorder": "#f92672",
"blockquoteBackground": "#2e2f2a",
"highlightBackground": "#e6db74",
"highlightText": "#272822"
},
"fonts": {
"body": "System",
"heading": "System",
"code": "SF Mono"
},
"sizes": {
"baseFontSize": 14,
"h1Size": 28,
"h2Size": 23,
"h3Size": 20,
"h4Size": 18,
"h5Size": 16,
"h6Size": 15,
"codeFontSize": 13,
"lineHeight": 1.7
},
"customCSS": ""
}
Loaded 100 of 253 files, more files were not shown because too many files have changed in this diff. Show more