Skip to content

Add OpenLiveWriterPost project and writer DLLs#470

Merged
ignatandrei merged 2 commits intomainfrom
LiveWriter
Apr 12, 2026
Merged

Add OpenLiveWriterPost project and writer DLLs#470
ignatandrei merged 2 commits intomainfrom
LiveWriter

Conversation

@ignatandrei
Copy link
Copy Markdown
Owner

@ignatandrei ignatandrei commented Apr 12, 2026

Add all.csv index and integrate an OpenLiveWriterPost helper into the v2 generator. Introduces a new OpenLiveWriterPost project (csproj + generator + WritePost helper) that saves generated HTML as OpenLiveWriter .wpost drafts, and invokes it from MultiGeneratorV2. Update Generator.sln and Generator.csproj to include the new project and switch the Generator to net8.0-windows with WindowsForms and x86 platform target. Add many prebuilt OpenLiveWriter-related DLLs and XML docs under v2/dlls/Writer, and update an example export (RSCG.xlsx).

Summary by CodeRabbit

  • New Features

    • Added Open Live Writer integration to generate and save blog posts directly from the app, including draft export and filename sanitization.
    • Added map preview/control pages and a default HTML/CSS post template for improved post rendering and location features.
  • Documentation

    • Added extensive API reference and XML docs for multiple libraries and runtime configuration files.

Add all.csv index and integrate an OpenLiveWriterPost helper into the v2 generator. Introduces a new OpenLiveWriterPost project (csproj + generator + WritePost helper) that saves generated HTML as OpenLiveWriter .wpost drafts, and invokes it from MultiGeneratorV2. Update Generator.sln and Generator.csproj to include the new project and switch the Generator to net8.0-windows with WindowsForms and x86 platform target. Add many prebuilt OpenLiveWriter-related DLLs and XML docs under v2/dlls/Writer, and update an example export (RSCG.xlsx).
Copilot AI review requested due to automatic review settings April 12, 2026 06:44
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 12, 2026

📝 Walkthrough

Walkthrough

Adds a new OpenLiveWriterPost project and integrates it into the post-writing flow of MultiGeneratorV2; updates Generator project to net8.0-windows/x86 with WinForms and many supporting config, XML docs, and HTML/CSS assets for Open Live Writer and related dependencies.

Changes

Cohort / File(s) Summary
Solution & Generator Project
v2/Generator.sln, v2/Generator/Generator.csproj
Updated VS solution metadata; changed Generator to net8.0-windows, enabled WinForms, set PlatformTarget to x86.
New OpenLiveWriterPost Project
v2/OpenLiveWriterPost/OpenLiveWriterPost.csproj
Added new SDK-style project targeting net8.0-windows with assembly HintPath references and NuGet/System.Drawing dependency.
Integration Hook
v2/Generator/MultiGeneratorV2.cs
After writing markdown, now calls OpenLiveWriterPost.WritePost.GeneratePostFromHtml(name, html) to hand off content to Open Live Writer flow.
OpenLiveWriterPost Implementation
v2/OpenLiveWriterPost/WritePost.cs, v2/OpenLiveWriterPost/OpenLiveWriterPostGenerator.cs
New public WritePost.GeneratePostFromHtml and internal OpenLiveWriterPostGenerator.SavePost implement BlogPost construction, drafts-folder resolution, OLW environment initialization, and saving/moving .wpost drafts.
Open Live Writer Config & Manifests
v2/dlls/Writer/OpenLiveWriter.exe.config, v2/dlls/Writer/OpenLiveWriter.PostEditor.dll.config, v2/dlls/Writer/OpenLiveWriter.BlogClient.dll.config, v2/dlls/Writer/OpenLiveWriter.exe.manifest
Added runtime binding redirects and application manifest (identity, DPI awareness, common-controls).
Auxiliary .config Files
v2/dlls/Writer/BlogRunner*.exe.config, v2/dlls/Writer/BlogRunnerGui.exe.config, v2/dlls/Writer/BlogRunnerReporter.exe.config, v2/dlls/Writer/MarketXmlGenerator.exe.config, v2/dlls/Writer/locutil.exe.config
Added runtime configuration and binding redirects for supporting executables.
Assembly XML Documentation
v2/dlls/Writer/*.xml
Added many XML doc files for dependencies (Google.Apis*, Microsoft.Threading.Tasks*, Approval*, PlatformSpellCheck, System.Net.Http.*, etc.).
Map Control & Templates
v2/dlls/Writer/html/map.html, v2/dlls/Writer/html/map-preview.html, v2/dlls/Writer/template/default.htm, v2/dlls/Writer/template/defaultstyle.css
Added VEMap-based map UI and preview pages, pushpin handling, and HTML/CSS post templates.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Gen as MultiGeneratorV2
    participant WP as WritePost
    participant OLWGen as OpenLiveWriterPostGenerator
    participant OLWLib as OpenLiveWriter Libraries
    participant FS as FileSystem

    User->>Gen: trigger post generation
    Gen->>Gen: render markdown -> temp file
    Gen->>Gen: launch Notepad
    Gen->>WP: GeneratePostFromHtml(name, html)
    WP->>WP: build BlogPost object
    WP->>FS: determine drafts folder & target .wpost path
    WP->>OLWGen: SavePost(post, filePath)
    OLWGen->>OLWLib: initialize OLW environment
    OLWGen->>OLWLib: create PostEditorFile & BlogPostEditingContext
    OLWGen->>FS: PostEditorFile.SaveBlogPost()
    OLWGen->>FS: detect/move created .wpost to filePath
    OLWGen-->>WP: return
    WP-->>Gen: return
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

Possibly related PRs

  • Add BoolParameterGenerator demo project #434 — Modifies v2/Generator/MultiGeneratorV2.cs at the same WrotePost integration point; likely overlapping changes to post-generation behavior.
  • firist #392 — Changes in v2/Generator/MultiGeneratorV2.cs affecting related post-processing/control flow in the same class.

Poem

🐰 I hopped through code and draft directories bright,
Turned HTML crumbs into a Writer-bound bite.
A GUID, a title, a tidy wpost tucked tight—
Hooray for drafts found and moved just right! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely summarizes the main changes: adding a new OpenLiveWriterPost project and Writer DLLs to the v2 generator codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch LiveWriter

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an OpenLiveWriter integration path to the v2 generator so generated HTML can also be saved as OpenLiveWriter .wpost drafts, alongside adding an all.csv index and vendoring required OpenLiveWriter/related binaries under v2/dlls/Writer.

Changes:

  • Introduces a new OpenLiveWriterPost project (net8.0-windows/x86, WinForms enabled) to generate .wpost drafts from HTML.
  • Invokes OpenLiveWriter draft generation from MultiGeneratorV2.WrotePost.
  • Adds all.csv and a large set of OpenLiveWriter-related DLLs/config/templates under v2/dlls/Writer, and updates the solution/project references accordingly.

Reviewed changes

Copilot reviewed 31 out of 103 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
v2/OpenLiveWriterPost/WritePost.cs Creates a BlogPost from HTML and attempts to save it as a .wpost draft in the OLW drafts folder.
v2/OpenLiveWriterPost/OpenLiveWriterPostGenerator.cs Initializes OpenLiveWriter environment and saves a post via PostEditorFile.
v2/OpenLiveWriterPost/OpenLiveWriterPost.csproj New helper project targeting net8.0-windows x86 with references to vendored OpenLiveWriter assemblies.
v2/Generator/MultiGeneratorV2.cs Calls the OpenLiveWriter draft generation during WrotePost.
v2/Generator/Generator.csproj Switches generator to net8.0-windows, enables WinForms/x86, and references OpenLiveWriterPost.
v2/Generator.sln Adds the OpenLiveWriterPost project to the solution (and updates VS header).
v2/dlls/Writer/template/defaultstyle.css Adds OLW template CSS asset.
v2/dlls/Writer/template/default.htm Adds OLW template HTML asset.
v2/dlls/Writer/System.Net.Http.Primitives.xml Adds XML docs for a vendored dependency.
v2/dlls/Writer/System.Net.Http.Extensions.xml Adds XML docs for a vendored dependency.
v2/dlls/Writer/PostBuild.CreateInstaller.dll Adds vendored OpenLiveWriter-related binary.
v2/dlls/Writer/PlatformSpellCheck.xml Adds XML docs for a vendored dependency.
v2/dlls/Writer/PlatformSpellCheck.dll Adds vendored spellcheck binary.
v2/dlls/Writer/OpenLiveWriter.Tests.dll Adds vendored OpenLiveWriter binary.
v2/dlls/Writer/OpenLiveWriter.PostEditor.dll.config Adds runtime binding redirects/config for a vendored assembly.
v2/dlls/Writer/OpenLiveWriter.exe.manifest Adds manifest for vendored OpenLiveWriter executable.
v2/dlls/Writer/OpenLiveWriter.exe.config Adds runtime binding redirects/config for vendored OpenLiveWriter executable.
v2/dlls/Writer/OpenLiveWriter.BlogClient.dll.config Adds runtime binding redirects/config for a vendored assembly.
v2/dlls/Writer/Microsoft.Threading.Tasks.Extensions.xml Adds XML docs for a vendored dependency.
v2/dlls/Writer/MarketXmlGenerator.exe.config Adds config for vendored tool.
v2/dlls/Writer/MarketXmlGenerator.exe Adds vendored tool binary.
v2/dlls/Writer/locutil.exe.config Adds config for vendored tool.
v2/dlls/Writer/html/map.html Adds vendored HTML asset.
v2/dlls/Writer/html/map-preview.html Adds vendored HTML asset.
v2/dlls/Writer/Google.Apis.PlatformServices.dll Adds vendored dependency binary.
v2/dlls/Writer/Google.Apis.Auth.PlatformServices.dll Adds vendored dependency binary.
v2/dlls/Writer/DeltaCompressionDotNet.PatchApi.dll Adds vendored dependency binary.
v2/dlls/Writer/DeltaCompressionDotNet.MsDelta.dll Adds vendored dependency binary.
v2/dlls/Writer/DeltaCompressionDotNet.dll Adds vendored dependency binary.
v2/dlls/Writer/BlogRunnerReporter.exe.config Adds config for vendored tool.
v2/dlls/Writer/BlogRunnerReporter.exe Adds vendored tool binary.
v2/dlls/Writer/BlogRunnerGui.exe.config Adds config for vendored tool.
v2/dlls/Writer/BlogRunner.exe.config Adds config for vendored tool.
v2/dlls/Writer/BlogRunner.exe Adds vendored tool binary.
v2/dlls/Writer/ApprovalUtilities.xml Adds XML docs for a vendored dependency.
v2/dlls/Writer/ApprovalUtilities.Net45.xml Adds XML docs for a vendored dependency.
v2/dlls/Writer/ApprovalUtilities.Net45.dll Adds vendored dependency binary.
v2/dlls/Writer/ApprovalTests.xml Adds XML docs for a vendored dependency.
all.csv Adds a root-level CSV index (appears duplicated with v2/Generator/all.csv).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread v2/OpenLiveWriterPost/OpenLiveWriterPostGenerator.cs Outdated
Comment thread v2/OpenLiveWriterPost/OpenLiveWriterPostGenerator.cs Outdated
Comment thread v2/OpenLiveWriterPost/WritePost.cs Outdated
Comment thread v2/OpenLiveWriterPost/WritePost.cs Outdated
await File.WriteAllTextAsync(file, output);
Process.Start("notepad.exe", file);
//Console.WriteLine(output);
OpenLiveWriterPost.WritePost.GeneratePostFromHtml(it.Generator.Name ?? "", output);
Comment thread v2/Generator.sln Outdated
Comment thread all.csv
Comment on lines +1 to +5
Nr,Key,Source,Category
1,ThisAssembly, https://github.com/devlooped/ThisAssembly,EnhancementProject
2,RSCG_TimeBombComment, https://github.com/ignatandrei/RSCG_TimeBombComment,EnhancementProject
3,System.Text.Json, https://github.com/dotnet/runtime,Serializer
4,RSCG_Utils, https://github.com/ignatandrei/RSCG_Utils,FilesToCode
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🧹 Nitpick comments (3)
v2/OpenLiveWriterPost/OpenLiveWriterPost.csproj (1)

13-25: Consider adding <Private> consistently to all OpenLiveWriter references.

Only OpenLiveWriter.Interop has <Private>true</Private>. For clarity and to ensure all dependencies are copied to the output, consider adding this element to the other OpenLiveWriter references as well, or document why only Interop needs it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v2/OpenLiveWriterPost/OpenLiveWriterPost.csproj` around lines 13 - 25, The
project currently only sets <Private>true</Private> for the
OpenLiveWriter.Interop Reference; update the other OpenLiveWriter references
(OpenLiveWriter.CoreServices, OpenLiveWriter.Extensibility,
OpenLiveWriter.PostEditor) to include <Private>true</Private> as well so those
DLLs are copied to the build output (or alternatively add a brief
comment/documentation in the .csproj explaining why only OpenLiveWriter.Interop
is private if that is intentional). Ensure you modify the Reference elements
named OpenLiveWriter.CoreServices, OpenLiveWriter.Extensibility, and
OpenLiveWriter.PostEditor to include the <Private>true</Private> element for
consistent behavior.
v2/dlls/Writer/OpenLiveWriter.exe.config (1)

4-27: Inconsistent config structure: multiple <assemblyBinding> elements.

Unlike other config files in this PR (e.g., OpenLiveWriter.PostEditor.dll.config, OpenLiveWriter.BlogClient.dll.config) which use a single <assemblyBinding> element containing all <dependentAssembly> children, this file uses separate <assemblyBinding> blocks for each redirect.

Consider consolidating for consistency, though this is functionally equivalent.

Proposed consolidation
   <runtime>
-    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
-      <dependentAssembly>
-        <assemblyIdentity name="DeltaCompressionDotNet.MsDelta" publicKeyToken="46b2138a390abf55" culture="neutral" />
-        <bindingRedirect oldVersion="0.0.0.0-1.1.0.0" newVersion="1.1.0.0" />
-      </dependentAssembly>
-    </assemblyBinding>
-    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
-      <dependentAssembly>
-        <assemblyIdentity name="Google.Apis.Core" publicKeyToken="4b01fa6e34db77ab" culture="neutral" />
-        <bindingRedirect oldVersion="0.0.0.0-1.39.0.0" newVersion="1.39.0.0" />
-      </dependentAssembly>
-    </assemblyBinding>
-    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
-      <dependentAssembly>
-        <assemblyIdentity name="Google.Apis" publicKeyToken="4b01fa6e34db77ab" culture="neutral" />
-        <bindingRedirect oldVersion="0.0.0.0-1.39.0.0" newVersion="1.39.0.0" />
-      </dependentAssembly>
-    </assemblyBinding>
-    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
-      <dependentAssembly>
-        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
-        <bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
-      </dependentAssembly>
-    </assemblyBinding>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="DeltaCompressionDotNet.MsDelta" publicKeyToken="46b2138a390abf55" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-1.1.0.0" newVersion="1.1.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Google.Apis.Core" publicKeyToken="4b01fa6e34db77ab" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-1.39.0.0" newVersion="1.39.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Google.Apis" publicKeyToken="4b01fa6e34db77ab" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-1.39.0.0" newVersion="1.39.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
+      </dependentAssembly>
+    </assemblyBinding>
   </runtime>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v2/dlls/Writer/OpenLiveWriter.exe.config` around lines 4 - 27, The config
uses multiple <assemblyBinding> elements each with xmlns instead of one
consolidated block; consolidate all <dependentAssembly> entries (those
containing <assemblyIdentity name="DeltaCompressionDotNet.MsDelta" ...>,
<assemblyIdentity name="Google.Apis.Core" ...>, <assemblyIdentity
name="Google.Apis" ...>, <assemblyIdentity name="Newtonsoft.Json" ...>) into a
single <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> element,
preserving each <bindingRedirect oldVersion="..." newVersion="..."> and
<assemblyIdentity> exactly as-is, and remove the duplicate <assemblyBinding>
blocks so the XML remains valid and consistent with the other config files.
v2/Generator/Generator.csproj (1)

5-8: Platform restriction: Generator now requires Windows x86.

The switch to net8.0-windows with UseWindowsForms and PlatformTarget=x86 means the Generator can no longer run on non-Windows platforms or as a 64-bit process. This is required due to the OpenLiveWriterPost dependency's use of legacy OpenLiveWriter DLLs.

Consider whether this constraint is acceptable for all build/CI environments where the Generator runs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v2/Generator/Generator.csproj` around lines 5 - 8, The project file now
forces net8.0-windows, UseWindowsForms and PlatformTarget=x86 which restricts
builds to 32-bit Windows; either make these properties conditional or split the
project: remove unconditional <TargetFramework>, <UseWindowsForms> and
<PlatformTarget> entries and instead add an MSBuild Condition that sets
net8.0-windows/UseWindowsForms/PlatformTarget="x86" only when the
OpenLiveWriterPost dependency is present (or when a build property like
UseOpenLiveWriter is true), or create a separate Windows-specific csproj that
references the OpenLiveWriterPost bits so non-Windows/64-bit CI can build the
main Generator project without the Windows/x86 constraint; update
package/ItemGroup references to be conditioned on that same flag.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@v2/dlls/Writer/html/map-preview.html`:
- Line 77: The code uses string-eval in setTimeout which allows script injection
via the scene variable; instead, stop building executable code and pass a real
function that calls initBirdseyeScene with the scene value (capture the scene
variable in a closure or use an arrow function) and keep the 200ms delay; update
the call site for setTimeout and remove the string concatenation so setTimeout
receives a function instead of a string.
- Line 103: The onchangeview handler is detached using the wrong function
(setMapLoaded) while it was attached as setBirdeyeMapLoaded; update the
DetachEvent call to remove the same handler that was attached by replacing the
second argument to map.DetachEvent("onchangeview", ...) with setBirdeyeMapLoaded
so the attached listener is actually removed (references: setBirdeyeMapLoaded,
setMapLoaded, map.DetachEvent).

In `@v2/dlls/Writer/html/map.html`:
- Around line 202-208: In processEvent, guard the lookup of
propertyChangeHandlers[e.name] before invoking it: verify that
propertyChangeHandlers[e.name] exists and is a function (e.g., !==
undefined/null and typeof === "function") and only call it when valid; if
missing, optionally log or ignore the unknown property event to avoid throwing
and aborting the batch. This change should be applied around the invocation in
the processEvent function where propertyChangeHandlers[e.name](e) is currently
called.
- Around line 184-199: In processEvents(), don't block or drop remaining events
when evtCount > 25: remove the alert() and return. Instead, when the loop
exceeds the safety threshold (evtCount variable inside processEvents calling
jsMapController.NextEvent() and processEvent(e)), log a non-blocking warning
(e.g., console.warn) and reschedule continuation of processing (e.g., call
setTimeout(processEvents, 0) or requestIdleCallback) so remaining events are
processed asynchronously rather than being discarded; keep the existing
processEvent(e) and NextEvent() usage but replace the alert/return branch with a
non-blocking reschedule and break out of the tight loop.

In `@v2/dlls/Writer/OpenLiveWriter.BlogClient.dll.config`:
- Around line 9-16: There are two identical dependentAssembly entries for the
assemblyIdentity name="System.Net.Http.Primitives" with the same
bindingRedirect; remove the duplicate dependentAssembly block so only one
bindingRedirect remains for System.Net.Http.Primitives (locate the duplicate
dependentAssembly elements containing assemblyIdentity
name="System.Net.Http.Primitives" and bindingRedirect
oldVersion="0.0.0.0-4.2.29.0" newVersion="4.2.29.0" and delete one).

In `@v2/dlls/Writer/OpenLiveWriter.PostEditor.dll.config`:
- Around line 13-16: Remove the duplicate bindingRedirect for the assembly
DeltaCompressionDotNet.MsDelta and normalize the publicKeyToken casing; locate
the two <dependentAssembly> entries that reference assemblyIdentity
name="DeltaCompressionDotNet.MsDelta" (one using
publicKeyToken="46B2138A390ABF55" and the other "46b2138a390abf55"), delete one
of them and keep the other, ensuring the retained bindingRedirect has the
correct newVersion="1.1.0.0" and a consistently cased publicKeyToken (choose
either all-upper or all-lower) so only a single, unambiguous redirect for
DeltaCompressionDotNet.MsDelta remains.
- Around line 9-20: The config contains duplicate dependentAssembly entries for
the assemblyIdentity name="System.Net.Http.Primitives"; remove the redundant
dependentAssembly block (the second occurrence with the same assemblyIdentity
and bindingRedirect) so only one bindingRedirect for System.Net.Http.Primitives
(oldVersion="0.0.0.0-4.2.29.0" newVersion="4.2.29.0") remains, leaving other
entries like DeltaCompressionDotNet.MsDelta untouched.

In `@v2/dlls/Writer/template/default.htm`:
- Line 5: Replace the malformed META tag's content attribute so it declares the
charset correctly: update the Content-Type meta element (the META tag in
v2/dlls/Writer/template/default.htm) to include the "charset=" prefix before the
encoding value (e.g., change content="text/html; utf-8" to content="text/html;
charset=utf-8") and ensure proper casing/format for the attribute value.

In `@v2/dlls/Writer/template/defaultstyle.css`:
- Line 25: The CSS template contains raw template tokens ({body-font} and
{title-font}) in defaultstyle.css that break stylelint's CSS parsing
(CssSyntaxError); fix by updating the stylelint configuration to treat this file
as template-only—either add an ignoreFiles entry for the template path or add an
overrides entry that matches defaultstyle.css and sets a template-aware
customSyntax (e.g., postcss-html/postcss-markup) so stylelint will parse
template tokens instead of failing on {body-font}/{title-font}.

In `@v2/Generator.sln`:
- Around line 38-41: The solution maps the OpenLiveWriterPost project (GUID
DF7E2A72-9D51-4AC9-982D-5EDC36B5DA26) to Any CPU while the project declares
<PlatformTarget>x86</PlatformTarget> and manifest processorArchitecture="X86",
causing a platform mismatch; fix by making the platforms consistent: either
change the solution configuration entry for that project (the lines referencing
{DF7E2A72-9D51-4AC9-982D-5EDC36B5DA26}.Debug/Release|Any CPU.*) to use x86, or
update the project properties to use PlatformTarget AnyCPU and adjust the
manifest processorArchitecture accordingly if the x86-specific dependencies
allow it, then re-run a clean build to verify no runtime or dependency issues
remain.

In `@v2/OpenLiveWriterPost/OpenLiveWriterPostGenerator.cs`:
- Line 24: PostEditorFile.CreateNew is being passed a new DirectoryInfo
constructed from Path.GetDirectoryName(filePath) which can be null/empty; guard
against that by validating filePath and the result of
Path.GetDirectoryName(filePath) before creating DirectoryInfo (e.g., throw
ArgumentException or choose a safe default), and use the validated directory
when calling PostEditorFile.CreateNew so DirectoryInfo is never constructed from
a null path.
- Around line 19-20: The call to ApplicationEnvironment.Initialize uses
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
@"\Windows Live\Writer\") which passes a rooted second segment and causes the
ProgramFiles prefix to be dropped; update the second argument to a relative path
(e.g. "Windows Live\\Writer" or "Windows Live/Writer") or build the path with
Path.Combine parts (e.g. Path.Combine(..., "Windows Live", "Writer")) so the
Program Files base path is preserved when calling
ApplicationEnvironment.Initialize(Assembly.GetExecutingAssembly(), ...).

In `@v2/OpenLiveWriterPost/WritePost.cs`:
- Around line 25-29: The code currently deletes the existing draft (File.Delete
on filePath1) before calling OpenLiveWriterPostGenerator.SavePost, risking data
loss if SavePost throws; instead, write the new content to a temporary file
(e.g., filePath1 + ".tmp") via OpenLiveWriterPostGenerator.SavePost(post1,
tempPath), verify the save succeeded, then atomically replace the original (use
File.Replace or move/rename with overwrite) from tempPath to filePath1 so the
original is preserved on failure; ensure exceptions from SavePost leave the
original file intact and clean up the temp file on success or failure.
- Around line 22-23: The code builds fileName1 using post1.Title directly which
may contain invalid filesystem characters; update the logic that constructs
fileName1 so it sanitizes post1.Title (e.g., remove or replace
Path.GetInvalidFileNameChars() and trim/normalize whitespace, and ensure a
non-empty fallback name) before composing $"{sanitizedTitle}.wpost" and passing
it to Path.Combine with draftsFolder1; locate the fileName1 assignment and
replace it with the sanitized-title construction while keeping the rest
(Path.Combine(draftsFolder1, fileName1)) unchanged.

---

Nitpick comments:
In `@v2/dlls/Writer/OpenLiveWriter.exe.config`:
- Around line 4-27: The config uses multiple <assemblyBinding> elements each
with xmlns instead of one consolidated block; consolidate all
<dependentAssembly> entries (those containing <assemblyIdentity
name="DeltaCompressionDotNet.MsDelta" ...>, <assemblyIdentity
name="Google.Apis.Core" ...>, <assemblyIdentity name="Google.Apis" ...>,
<assemblyIdentity name="Newtonsoft.Json" ...>) into a single <assemblyBinding
xmlns="urn:schemas-microsoft-com:asm.v1"> element, preserving each
<bindingRedirect oldVersion="..." newVersion="..."> and <assemblyIdentity>
exactly as-is, and remove the duplicate <assemblyBinding> blocks so the XML
remains valid and consistent with the other config files.

In `@v2/Generator/Generator.csproj`:
- Around line 5-8: The project file now forces net8.0-windows, UseWindowsForms
and PlatformTarget=x86 which restricts builds to 32-bit Windows; either make
these properties conditional or split the project: remove unconditional
<TargetFramework>, <UseWindowsForms> and <PlatformTarget> entries and instead
add an MSBuild Condition that sets
net8.0-windows/UseWindowsForms/PlatformTarget="x86" only when the
OpenLiveWriterPost dependency is present (or when a build property like
UseOpenLiveWriter is true), or create a separate Windows-specific csproj that
references the OpenLiveWriterPost bits so non-Windows/64-bit CI can build the
main Generator project without the Windows/x86 constraint; update
package/ItemGroup references to be conditioned on that same flag.

In `@v2/OpenLiveWriterPost/OpenLiveWriterPost.csproj`:
- Around line 13-25: The project currently only sets <Private>true</Private> for
the OpenLiveWriter.Interop Reference; update the other OpenLiveWriter references
(OpenLiveWriter.CoreServices, OpenLiveWriter.Extensibility,
OpenLiveWriter.PostEditor) to include <Private>true</Private> as well so those
DLLs are copied to the build output (or alternatively add a brief
comment/documentation in the .csproj explaining why only OpenLiveWriter.Interop
is private if that is intentional). Ensure you modify the Reference elements
named OpenLiveWriter.CoreServices, OpenLiveWriter.Extensibility, and
OpenLiveWriter.PostEditor to include the <Private>true</Private> element for
consistent behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 14feaa0c-4f25-455a-ad79-b5a0a32ed02f

📥 Commits

Reviewing files that changed from the base of the PR and between 50081ad and 5790b1f.

⛔ Files ignored due to path filters (65)
  • all.csv is excluded by !**/*.csv
  • v2/dlls/Writer/ApprovalTests.dll is excluded by !**/*.dll
  • v2/dlls/Writer/ApprovalUtilities.Net45.dll is excluded by !**/*.dll
  • v2/dlls/Writer/ApprovalUtilities.dll is excluded by !**/*.dll
  • v2/dlls/Writer/BlogRunner.Core.dll is excluded by !**/*.dll
  • v2/dlls/Writer/BlogRunner.exe is excluded by !**/*.exe
  • v2/dlls/Writer/BlogRunnerGui.exe is excluded by !**/*.exe
  • v2/dlls/Writer/BlogRunnerReporter.exe is excluded by !**/*.exe
  • v2/dlls/Writer/DeltaCompressionDotNet.MsDelta.dll is excluded by !**/*.dll
  • v2/dlls/Writer/DeltaCompressionDotNet.PatchApi.dll is excluded by !**/*.dll
  • v2/dlls/Writer/DeltaCompressionDotNet.dll is excluded by !**/*.dll
  • v2/dlls/Writer/Google.Apis.Auth.PlatformServices.dll is excluded by !**/*.dll
  • v2/dlls/Writer/Google.Apis.Auth.dll is excluded by !**/*.dll
  • v2/dlls/Writer/Google.Apis.Blogger.v3.dll is excluded by !**/*.dll
  • v2/dlls/Writer/Google.Apis.Core.dll is excluded by !**/*.dll
  • v2/dlls/Writer/Google.Apis.Drive.v3.dll is excluded by !**/*.dll
  • v2/dlls/Writer/Google.Apis.PlatformServices.dll is excluded by !**/*.dll
  • v2/dlls/Writer/Google.Apis.dll is excluded by !**/*.dll
  • v2/dlls/Writer/ICSharpCode.SharpZipLib.dll is excluded by !**/*.dll
  • v2/dlls/Writer/MarketXmlGenerator.exe is excluded by !**/*.exe
  • v2/dlls/Writer/Microsoft.Threading.Tasks.Extensions.Desktop.dll is excluded by !**/*.dll
  • v2/dlls/Writer/Microsoft.Threading.Tasks.Extensions.dll is excluded by !**/*.dll
  • v2/dlls/Writer/Microsoft.Threading.Tasks.dll is excluded by !**/*.dll
  • v2/dlls/Writer/Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll is excluded by !**/*.dll
  • v2/dlls/Writer/Mono.Cecil.Mdb.dll is excluded by !**/*.dll
  • v2/dlls/Writer/Mono.Cecil.Pdb.dll is excluded by !**/*.dll
  • v2/dlls/Writer/Mono.Cecil.Rocks.dll is excluded by !**/*.dll
  • v2/dlls/Writer/Mono.Cecil.dll is excluded by !**/*.dll
  • v2/dlls/Writer/NUnit3.TestAdapter.dll is excluded by !**/*.dll
  • v2/dlls/Writer/Newtonsoft.Json.dll is excluded by !**/*.dll
  • v2/dlls/Writer/NuGet.Squirrel.dll is excluded by !**/*.dll
  • v2/dlls/Writer/OpenLiveWriter.Api.dll is excluded by !**/*.dll
  • v2/dlls/Writer/OpenLiveWriter.ApplicationFramework.dll is excluded by !**/*.dll
  • v2/dlls/Writer/OpenLiveWriter.BlogClient.dll is excluded by !**/*.dll
  • v2/dlls/Writer/OpenLiveWriter.BrowserControl.dll is excluded by !**/*.dll
  • v2/dlls/Writer/OpenLiveWriter.Controls.dll is excluded by !**/*.dll
  • v2/dlls/Writer/OpenLiveWriter.CoreServices.dll is excluded by !**/*.dll
  • v2/dlls/Writer/OpenLiveWriter.Extensibility.dll is excluded by !**/*.dll
  • v2/dlls/Writer/OpenLiveWriter.FileDestinations.dll is excluded by !**/*.dll
  • v2/dlls/Writer/OpenLiveWriter.HtmlEditor.dll is excluded by !**/*.dll
  • v2/dlls/Writer/OpenLiveWriter.HtmlParser.dll is excluded by !**/*.dll
  • v2/dlls/Writer/OpenLiveWriter.InternalWriterPlugin.dll is excluded by !**/*.dll
  • v2/dlls/Writer/OpenLiveWriter.Interop.Mshtml.dll is excluded by !**/*.dll
  • v2/dlls/Writer/OpenLiveWriter.Interop.SHDocVw.dll is excluded by !**/*.dll
  • v2/dlls/Writer/OpenLiveWriter.Interop.dll is excluded by !**/*.dll
  • v2/dlls/Writer/OpenLiveWriter.Localization.dll is excluded by !**/*.dll
  • v2/dlls/Writer/OpenLiveWriter.Mshtml.dll is excluded by !**/*.dll
  • v2/dlls/Writer/OpenLiveWriter.PostEditor.dll is excluded by !**/*.dll
  • v2/dlls/Writer/OpenLiveWriter.SpellChecker.dll is excluded by !**/*.dll
  • v2/dlls/Writer/OpenLiveWriter.Tests.dll is excluded by !**/*.dll
  • v2/dlls/Writer/OpenLiveWriter.UnitTest.dll is excluded by !**/*.dll
  • v2/dlls/Writer/OpenLiveWriter.exe is excluded by !**/*.exe
  • v2/dlls/Writer/PlatformSpellCheck.dll is excluded by !**/*.dll
  • v2/dlls/Writer/PostBuild.CreateInstaller.dll is excluded by !**/*.dll
  • v2/dlls/Writer/Splat.dll is excluded by !**/*.dll
  • v2/dlls/Writer/Squirrel.dll is excluded by !**/*.dll
  • v2/dlls/Writer/System.Net.Http.Extensions.dll is excluded by !**/*.dll
  • v2/dlls/Writer/System.Net.Http.Primitives.dll is excluded by !**/*.dll
  • v2/dlls/Writer/YamlDotNet.dll is excluded by !**/*.dll
  • v2/dlls/Writer/Zlib.Portable.dll is excluded by !**/*.dll
  • v2/dlls/Writer/locutil.exe is excluded by !**/*.exe
  • v2/dlls/Writer/nunit.engine.api.dll is excluded by !**/*.dll
  • v2/dlls/Writer/nunit.engine.dll is excluded by !**/*.dll
  • v2/dlls/Writer/nunit.framework.dll is excluded by !**/*.dll
  • v2/rscg_examples_site/static/exports/RSCG.xlsx is excluded by !**/*.xlsx
📒 Files selected for processing (38)
  • v2/Generator.sln
  • v2/Generator/Generator.csproj
  • v2/Generator/MultiGeneratorV2.cs
  • v2/OpenLiveWriterPost/OpenLiveWriterPost.csproj
  • v2/OpenLiveWriterPost/OpenLiveWriterPostGenerator.cs
  • v2/OpenLiveWriterPost/WritePost.cs
  • v2/dlls/Writer/ApprovalTests.xml
  • v2/dlls/Writer/ApprovalUtilities.Net45.xml
  • v2/dlls/Writer/ApprovalUtilities.xml
  • v2/dlls/Writer/BlogRunner.exe.config
  • v2/dlls/Writer/BlogRunnerGui.exe.config
  • v2/dlls/Writer/BlogRunnerReporter.exe.config
  • v2/dlls/Writer/Google.Apis.Auth.xml
  • v2/dlls/Writer/Google.Apis.Blogger.v3.xml
  • v2/dlls/Writer/Google.Apis.Core.xml
  • v2/dlls/Writer/Google.Apis.Drive.v3.xml
  • v2/dlls/Writer/Google.Apis.xml
  • v2/dlls/Writer/ICSharpCode.SharpZipLib.xml
  • v2/dlls/Writer/MarketXmlGenerator.exe.config
  • v2/dlls/Writer/Microsoft.Threading.Tasks.Extensions.Desktop.xml
  • v2/dlls/Writer/Microsoft.Threading.Tasks.Extensions.xml
  • v2/dlls/Writer/Microsoft.Threading.Tasks.xml
  • v2/dlls/Writer/Newtonsoft.Json.xml
  • v2/dlls/Writer/OpenLiveWriter.BlogClient.dll.config
  • v2/dlls/Writer/OpenLiveWriter.PostEditor.dll.config
  • v2/dlls/Writer/OpenLiveWriter.exe.config
  • v2/dlls/Writer/OpenLiveWriter.exe.manifest
  • v2/dlls/Writer/PlatformSpellCheck.xml
  • v2/dlls/Writer/System.Net.Http.Extensions.xml
  • v2/dlls/Writer/System.Net.Http.Primitives.xml
  • v2/dlls/Writer/YamlDotNet.xml
  • v2/dlls/Writer/Zlib.Portable.xml
  • v2/dlls/Writer/html/map-preview.html
  • v2/dlls/Writer/html/map.html
  • v2/dlls/Writer/locutil.exe.config
  • v2/dlls/Writer/nunit.framework.xml
  • v2/dlls/Writer/template/default.htm
  • v2/dlls/Writer/template/defaultstyle.css

{
map.LoadMap();
mapElement.runtimeStyle.display = "none";
setTimeout("initBirdseyeScene('" + scene + "')", 200);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove string-eval timeout to prevent query-parameter script injection.

Line 77 builds executable code from scene and passes it to setTimeout. Use a function callback instead.

Suggested fix
-			setTimeout("initBirdseyeScene('" + scene + "')", 200);
+			setTimeout(function () { initBirdseyeScene(scene); }, 200);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
setTimeout("initBirdseyeScene('" + scene + "')", 200);
setTimeout(function () { initBirdseyeScene(scene); }, 200);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v2/dlls/Writer/html/map-preview.html` at line 77, The code uses string-eval
in setTimeout which allows script injection via the scene variable; instead,
stop building executable code and pass a real function that calls
initBirdseyeScene with the scene value (capture the scene variable in a closure
or use an arrow function) and keep the 200ms delay; update the call site for
setTimeout and remove the string concatenation so setTimeout receives a function
instead of a string.

}

function setBirdeyeMapLoaded(){
map.DetachEvent("onchangeview", setMapLoaded);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Detach the same handler that was attached for onchangeview.

Line 103 detaches setMapLoaded, but Line 97 attaches setBirdeyeMapLoaded. The current detach is ineffective and leaves the handler active.

Suggested fix
-		map.DetachEvent("onchangeview", setMapLoaded);
+		map.DetachEvent("onchangeview", setBirdeyeMapLoaded);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v2/dlls/Writer/html/map-preview.html` at line 103, The onchangeview handler
is detached using the wrong function (setMapLoaded) while it was attached as
setBirdeyeMapLoaded; update the DetachEvent call to remove the same handler that
was attached by replacing the second argument to map.DetachEvent("onchangeview",
...) with setBirdeyeMapLoaded so the attached listener is actually removed
(references: setBirdeyeMapLoaded, setMapLoaded, map.DetachEvent).

Comment on lines +184 to +199
function processEvents()
{
var evtCount = 0;
var e = jsMapController.NextEvent();
while(e != null)
{
evtCount++;
if(evtCount > 25)
{
alert("Bug alert: recursive event loop: " + e);
return;
}

processEvent(e);
e = jsMapController.NextEvent();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't convert normal event bursts into a blocking alert() and dropped updates.

Any queue longer than 25 events exits early and leaves the rest unprocessed. A larger pushpin batch or property burst will stall synchronization and surface a modal dialog in the editor.

Proposed fix
 function processEvents()
 {
-		var evtCount = 0;
-		var e = jsMapController.NextEvent();
-		while(e != null)
-		{
-			evtCount++;
-			if(evtCount > 25)
-			{
-				alert("Bug alert: recursive event loop: " + e);
-				return;
-			}
-			
-			processEvent(e);				
-			e = jsMapController.NextEvent();
-		}
+		var evtCount = 0;
+		var maxEventsPerTick = 25;
+		var e = jsMapController.NextEvent();
+		while(e != null && evtCount < maxEventsPerTick)
+		{
+			processEvent(e);
+			evtCount++;
+			e = jsMapController.NextEvent();
+		}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v2/dlls/Writer/html/map.html` around lines 184 - 199, In processEvents(),
don't block or drop remaining events when evtCount > 25: remove the alert() and
return. Instead, when the loop exceeds the safety threshold (evtCount variable
inside processEvents calling jsMapController.NextEvent() and processEvent(e)),
log a non-blocking warning (e.g., console.warn) and reschedule continuation of
processing (e.g., call setTimeout(processEvents, 0) or requestIdleCallback) so
remaining events are processed asynchronously rather than being discarded; keep
the existing processEvent(e) and NextEvent() usage but replace the alert/return
branch with a non-blocking reschedule and break out of the tight loop.

Comment on lines +202 to +208
function processEvent(e)
{
if(e.type == 1) //propertyChanged
{
//alert("propertyChanged: " + e.name + " " + e.value);
//invoke the handler for the specified property
propertyChangeHandlers[e.name](e);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard unknown property names before invoking a handler.

propertyChangeHandlers[e.name] can be undefined. One unexpected host event will throw and abort the current batch.

Proposed fix
 	if(e.type == 1) //propertyChanged
 	{
 			//alert("propertyChanged: " + e.name + " " + e.value);
 			//invoke the handler for the specified property
-			propertyChangeHandlers[e.name](e);
+			var handler = propertyChangeHandlers[e.name];
+			if(handler != null)
+				handler(e);
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v2/dlls/Writer/html/map.html` around lines 202 - 208, In processEvent, guard
the lookup of propertyChangeHandlers[e.name] before invoking it: verify that
propertyChangeHandlers[e.name] exists and is a function (e.g., !==
undefined/null and typeof === "function") and only call it when valid; if
missing, optionally log or ignore the unknown property event to avoid throwing
and aborting the batch. This change should be applied around the invocation in
the processEvent function where propertyChangeHandlers[e.name](e) is currently
called.

Comment on lines +9 to +16
<dependentAssembly>
<assemblyIdentity name="System.Net.Http.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.29.0" newVersion="4.2.29.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Net.Http.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.29.0" newVersion="4.2.29.0" />
</dependentAssembly>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Duplicate binding redirect for System.Net.Http.Primitives.

Lines 9-12 and 13-16 contain identical binding redirects for System.Net.Http.Primitives. Remove the duplicate entry.

Proposed fix - remove duplicate
       <dependentAssembly>
         <assemblyIdentity name="System.Net.Http.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
         <bindingRedirect oldVersion="0.0.0.0-4.2.29.0" newVersion="4.2.29.0" />
       </dependentAssembly>
-      <dependentAssembly>
-        <assemblyIdentity name="System.Net.Http.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
-        <bindingRedirect oldVersion="0.0.0.0-4.2.29.0" newVersion="4.2.29.0" />
-      </dependentAssembly>
       <dependentAssembly>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<dependentAssembly>
<assemblyIdentity name="System.Net.Http.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.29.0" newVersion="4.2.29.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Net.Http.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.29.0" newVersion="4.2.29.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Net.Http.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.29.0" newVersion="4.2.29.0" />
</dependentAssembly>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v2/dlls/Writer/OpenLiveWriter.BlogClient.dll.config` around lines 9 - 16,
There are two identical dependentAssembly entries for the assemblyIdentity
name="System.Net.Http.Primitives" with the same bindingRedirect; remove the
duplicate dependentAssembly block so only one bindingRedirect remains for
System.Net.Http.Primitives (locate the duplicate dependentAssembly elements
containing assemblyIdentity name="System.Net.Http.Primitives" and
bindingRedirect oldVersion="0.0.0.0-4.2.29.0" newVersion="4.2.29.0" and delete
one).

Comment thread v2/Generator.sln
Comment on lines +38 to +41
{DF7E2A72-9D51-4AC9-982D-5EDC36B5DA26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DF7E2A72-9D51-4AC9-982D-5EDC36B5DA26}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DF7E2A72-9D51-4AC9-982D-5EDC36B5DA26}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DF7E2A72-9D51-4AC9-982D-5EDC36B5DA26}.Release|Any CPU.Build.0 = Release|Any CPU
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check platform configuration across solution and project files

echo "=== Solution platform configurations for OpenLiveWriterPost ==="
rg "DF7E2A72-9D51-4AC9-982D-5EDC36B5DA26" v2/Generator.sln -A 1

echo -e "\n=== Project PlatformTarget setting ==="
rg "PlatformTarget" v2/OpenLiveWriterPost/OpenLiveWriterPost.csproj

echo -e "\n=== Manifest processorArchitecture ==="
rg "processorArchitecture" v2/dlls/Writer/OpenLiveWriter.exe.manifest

Repository: ignatandrei/RSCG_Examples

Length of output: 862


Platform configuration mismatch between solution and project files.

The solution (v2/Generator.sln) maps the OpenLiveWriterPost project to Any CPU configuration, while the project file specifies <PlatformTarget>x86</PlatformTarget> and the manifest declares processorArchitecture="X86". This mismatch will cause build inconsistencies or runtime issues, particularly with the x86-specific OpenLiveWriter dependencies.

Resolve by either:

  1. Changing the solution platform configuration to x86, or
  2. Updating the project's PlatformTarget to AnyCPU (if compatible with x86-specific dependencies)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v2/Generator.sln` around lines 38 - 41, The solution maps the
OpenLiveWriterPost project (GUID DF7E2A72-9D51-4AC9-982D-5EDC36B5DA26) to Any
CPU while the project declares <PlatformTarget>x86</PlatformTarget> and manifest
processorArchitecture="X86", causing a platform mismatch; fix by making the
platforms consistent: either change the solution configuration entry for that
project (the lines referencing
{DF7E2A72-9D51-4AC9-982D-5EDC36B5DA26}.Debug/Release|Any CPU.*) to use x86, or
update the project properties to use PlatformTarget AnyCPU and adjust the
manifest processorArchitecture accordingly if the x86-specific dependencies
allow it, then re-run a clean build to verify no runtime or dependency issues
remain.

Comment thread v2/OpenLiveWriterPost/OpenLiveWriterPostGenerator.cs Outdated
Comment thread v2/OpenLiveWriterPost/OpenLiveWriterPostGenerator.cs Outdated
Comment thread v2/OpenLiveWriterPost/WritePost.cs Outdated
Comment on lines +25 to +29
if(File.Exists(filePath1)) File.Delete(filePath1);

Console.WriteLine("Creating hardcoded blog post...");

OpenLiveWriterPostGenerator.SavePost(post1, filePath1);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid deleting the existing draft before confirming save succeeds.

Line 25 deletes the existing .wpost first. If Line 29 fails, the old draft is lost.

Suggested fix
-        if(File.Exists(filePath1)) File.Delete(filePath1);
-
-        Console.WriteLine("Creating hardcoded blog post...");
-        
-        OpenLiveWriterPostGenerator.SavePost(post1, filePath1);
+        var backupPath = filePath1 + ".bak";
+        if (File.Exists(filePath1))
+            File.Copy(filePath1, backupPath, overwrite: true);
+
+        Console.WriteLine("Creating hardcoded blog post...");
+
+        try
+        {
+            OpenLiveWriterPostGenerator.SavePost(post1, filePath1);
+            if (File.Exists(backupPath)) File.Delete(backupPath);
+        }
+        catch
+        {
+            if (File.Exists(backupPath))
+                File.Move(backupPath, filePath1, overwrite: true);
+            throw;
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if(File.Exists(filePath1)) File.Delete(filePath1);
Console.WriteLine("Creating hardcoded blog post...");
OpenLiveWriterPostGenerator.SavePost(post1, filePath1);
var backupPath = filePath1 + ".bak";
if (File.Exists(filePath1))
File.Copy(filePath1, backupPath, overwrite: true);
Console.WriteLine("Creating hardcoded blog post...");
try
{
OpenLiveWriterPostGenerator.SavePost(post1, filePath1);
if (File.Exists(backupPath)) File.Delete(backupPath);
}
catch
{
if (File.Exists(backupPath))
File.Move(backupPath, filePath1, overwrite: true);
throw;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v2/OpenLiveWriterPost/WritePost.cs` around lines 25 - 29, The code currently
deletes the existing draft (File.Delete on filePath1) before calling
OpenLiveWriterPostGenerator.SavePost, risking data loss if SavePost throws;
instead, write the new content to a temporary file (e.g., filePath1 + ".tmp")
via OpenLiveWriterPostGenerator.SavePost(post1, tempPath), verify the save
succeeded, then atomically replace the original (use File.Replace or move/rename
with overwrite) from tempPath to filePath1 so the original is preserved on
failure; ensure exceptions from SavePost leave the original file intact and
clean up the temp file on success or failure.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
v2/OpenLiveWriterPost/OpenLiveWriterPostGenerator.cs (3)

12-15: Consider adding parameter validation.

The public entry point does not validate post or filePath. While the current caller (WritePost.GeneratePostFromHtml) provides valid values, adding null checks would make this method more robust for future callers.

💡 Suggested validation
 public static void SavePost(BlogPost post, string filePath)
 {
+    ArgumentNullException.ThrowIfNull(post);
+    ArgumentException.ThrowIfNullOrWhiteSpace(filePath);
+
     SavePostAsStructuredStorage(post, filePath);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v2/OpenLiveWriterPost/OpenLiveWriterPostGenerator.cs` around lines 12 - 15,
SavePost currently forwards to SavePostAsStructuredStorage without validating
inputs; add guards at the start of the public SavePost(BlogPost post, string
filePath) to validate that post is not null (throw
ArgumentNullException(nameof(post))) and that filePath is not null or
empty/whitespace (throw ArgumentException or ArgumentNullException for
nameof(filePath))); after validation call SavePostAsStructuredStorage(post,
filePath) as before. This ensures the public entrypoint is robust for future
callers.

49-72: Consider simplifying with LINQ and HashSet.

The nested loop has O(n×m) complexity. While acceptable for typical draft counts, a HashSet lookup would be cleaner and O(n).

💡 Simplified implementation
 private static string FindCreatedDraftPath(string directoryPath, string[] existingDrafts)
 {
     string[] currentDrafts = Directory.GetFiles(directoryPath, "*.wpost");
+    var existingSet = new HashSet<string>(existingDrafts, StringComparer.OrdinalIgnoreCase);
 
-    foreach (string currentDraft in currentDrafts)
-    {
-        bool existedBefore = false;
-        foreach (string existingDraft in existingDrafts)
-        {
-            if (string.Equals(currentDraft, existingDraft, StringComparison.OrdinalIgnoreCase))
-            {
-                existedBefore = true;
-                break;
-            }
-        }
-
-        if (!existedBefore)
-        {
-            return currentDraft;
-        }
-    }
+    var newDraft = currentDrafts.FirstOrDefault(d => !existingSet.Contains(d));
+    if (newDraft != null)
+        return newDraft;
 
     throw new IOException($"Unable to determine the draft file created in '{directoryPath}'.");
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v2/OpenLiveWriterPost/OpenLiveWriterPostGenerator.cs` around lines 49 - 72,
The nested loops in FindCreatedDraftPath produce O(n×m) comparisons and are
verbose; replace the manual existence check by constructing a HashSet<string>
(using OrdinalIgnoreCase) from existingDrafts and then iterate currentDrafts to
return the first draft not contained in that set (currentDrafts =
Directory.GetFiles(...)). This simplifies logic, improves lookup to O(1) per
item, and preserves the existing exception behavior when no new draft is found.

30-46: Minor race window in draft detection.

The snapshot-compare approach (lines 31, 37) to identify the created draft has a small race window where another process could create a .wpost file between the snapshot and the post-save scan, potentially causing incorrect file identification. For single-user desktop tooling this is likely acceptable, but worth noting.

Additionally, the delete-then-move pattern (lines 40-45) has a TOCTOU window. Consider using File.Move with overwrite if targeting .NET 5+.

💡 Simplify with File.Move overwrite (if .NET 5+)
-        if (!string.Equals(createdDraftPath, filePath, StringComparison.OrdinalIgnoreCase))
-        {
-            if (File.Exists(filePath))
-            {
-                File.Delete(filePath);
-            }
-
-            File.Move(createdDraftPath, filePath);
-        }
+        if (!string.Equals(createdDraftPath, filePath, StringComparison.OrdinalIgnoreCase))
+        {
+            File.Move(createdDraftPath, filePath, overwrite: true);
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v2/OpenLiveWriterPost/OpenLiveWriterPostGenerator.cs` around lines 30 - 46,
The draft-detection uses a pre-save snapshot + post-save scan
(DirectoryInfo/FindCreatedDraftPath after
PostEditorFile.CreateNew/pef.SaveBlogPost) which has a race window, and the
current delete-then-move pattern (File.Delete then File.Move) has a TOCTOU; fix
by either (A) making PostEditorFile.CreateNew or pef.SaveBlogPost return the
exact created file path (preferred) so you can use that directly, or (B) if
changing those signatures is undesirable, create a unique marker/target name in
the target directory before saving (so you can reliably identify the new file)
or use a FileSystemWatcher to detect the created .wpost; then replace the
delete-then-move with the atomic overwrite-capable API:
File.Move(createdDraftPath, filePath, overwrite: true) (requires .NET 5+),
referencing FindCreatedDraftPath, PostEditorFile.CreateNew, pef.SaveBlogPost,
createdDraftPath and filePath.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@v2/OpenLiveWriterPost/OpenLiveWriterPostGenerator.cs`:
- Around line 12-15: SavePost currently forwards to SavePostAsStructuredStorage
without validating inputs; add guards at the start of the public
SavePost(BlogPost post, string filePath) to validate that post is not null
(throw ArgumentNullException(nameof(post))) and that filePath is not null or
empty/whitespace (throw ArgumentException or ArgumentNullException for
nameof(filePath))); after validation call SavePostAsStructuredStorage(post,
filePath) as before. This ensures the public entrypoint is robust for future
callers.
- Around line 49-72: The nested loops in FindCreatedDraftPath produce O(n×m)
comparisons and are verbose; replace the manual existence check by constructing
a HashSet<string> (using OrdinalIgnoreCase) from existingDrafts and then iterate
currentDrafts to return the first draft not contained in that set (currentDrafts
= Directory.GetFiles(...)). This simplifies logic, improves lookup to O(1) per
item, and preserves the existing exception behavior when no new draft is found.
- Around line 30-46: The draft-detection uses a pre-save snapshot + post-save
scan (DirectoryInfo/FindCreatedDraftPath after
PostEditorFile.CreateNew/pef.SaveBlogPost) which has a race window, and the
current delete-then-move pattern (File.Delete then File.Move) has a TOCTOU; fix
by either (A) making PostEditorFile.CreateNew or pef.SaveBlogPost return the
exact created file path (preferred) so you can use that directly, or (B) if
changing those signatures is undesirable, create a unique marker/target name in
the target directory before saving (so you can reliably identify the new file)
or use a FileSystemWatcher to detect the created .wpost; then replace the
delete-then-move with the atomic overwrite-capable API:
File.Move(createdDraftPath, filePath, overwrite: true) (requires .NET 5+),
referencing FindCreatedDraftPath, PostEditorFile.CreateNew, pef.SaveBlogPost,
createdDraftPath and filePath.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b27e64d9-bdef-458e-a51e-14834d9c4a66

📥 Commits

Reviewing files that changed from the base of the PR and between 5790b1f and 218a16f.

📒 Files selected for processing (3)
  • v2/Generator.sln
  • v2/OpenLiveWriterPost/OpenLiveWriterPostGenerator.cs
  • v2/OpenLiveWriterPost/WritePost.cs
✅ Files skipped from review due to trivial changes (1)
  • v2/Generator.sln
🚧 Files skipped from review as they are similar to previous changes (1)
  • v2/OpenLiveWriterPost/WritePost.cs

@ignatandrei ignatandrei merged commit 8c383b6 into main Apr 12, 2026
3 checks passed
@ignatandrei ignatandrei deleted the LiveWriter branch April 12, 2026 07:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants