Skip to content

feat: add HTML to HSML conversion mode - #2

Merged
Shinigami92 merged 4 commits into
mainfrom
feat-convert-html
Apr 10, 2026
Merged

Shinigami92 merged 4 commits into
mainfrom
feat-convert-html

Conversation

@Shinigami92

@Shinigami92 Shinigami92 commented Apr 10, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Added HTML→HSML conversion mode with mode toggle in the sidebar.
    • New HTML editor and HSML read-only output panels; formatter/compile controls hidden in convert mode.
    • URL-hash support for saving/restoring convert sessions (h: prefix).
  • Usability

    • Tab inserts two spaces; Shift+Tab removes two-space indentation in the editor.
  • Tests

    • Expanded end-to-end coverage for conversion, indentation, mode switching, URL-hash restoration, and UI visibility.

@Shinigami92 Shinigami92 self-assigned this Apr 10, 2026
@Shinigami92 Shinigami92 added the c: feature Request for new feature label Apr 10, 2026
@coderabbitai

coderabbitai Bot commented Apr 10, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a conversion-mode toggle (compile ↔ convert), new HtmlEditor and HsmlOutput components, refactors editor state to support dual pipelines with prefixed hash persistence, updates App and Sidebar to render per-mode, adjusts editors’ Tab/Shift-Tab behavior, and expands e2e tests to cover conversion flow and indentation.

Changes

Cohort / File(s) Summary
E2E Tests
e2e/playground.spec.ts
Renamed locator constants, added indentation suite (Tab / Shift+Tab behavior), expanded convert-mode tests (mode switching, default HTML content, HTML→HSML conversion, hidden compile options, h: hash prefix, restore from #h:<base64>, and switching back to compile).
App Layout
src/App.vue
Conditional rendering based on conversionMode: shows HsmlEditor+HtmlOutput for compile, HtmlEditor+HsmlOutput for convert. Imports new components and reads conversionMode from state.
New Components
src/components/HtmlEditor.vue, src/components/HsmlOutput.vue
HtmlEditor: writable CodeMirror HTML editor, two-space Tab insert, Shift+Tab outdent, theme compartment, sync with htmlInput. HsmlOutput: read-only HSML CodeMirror view, theme compartment, document diff update on hsmlOutput changes.
Editor Updates
src/components/HsmlEditor.vue
Replaced tabSize keymap with indentKeymap; preserved Tab insert and added Shift+Tab outdent handling.
Sidebar / UI Controls
src/components/SidebarPanel.vue
Adds conversionMode toggle UI, conditions formatter/compiler/diagnostics controls to show only in compile mode, updates button styling per active mode.
State Management
src/composables/useEditorState.ts
Introduces ConversionMode type and conversionMode reactive state; separate htmlInput/hsmlOutput/convertError for convert flow; readFromHash/writeToHash with c:/h: prefixes; mode-switch logic transfers outputs to inputs and triggers relevant transforms with debounced watchers.
TypeScript Config
tsconfig.e2e.json, tsconfig.json
Adds tsconfig.e2e.json for e2e TypeScript settings and appends it to root references in tsconfig.json.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I nibbled keys and hopped between,

Compile or Convert — what a scene!
Two-space tabs, a gentle shove,
Hashes carry tales thereof.
Bravo — editors dance, code clean.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 title accurately summarizes the main feature addition: a new HTML to HSML conversion mode that allows bidirectional conversion, complementing the existing HSML to HTML compilation capability.

✏️ 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 feat-convert-html

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.

@Shinigami92
Shinigami92 marked this pull request as ready for review April 10, 2026 20:09

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
e2e/playground.spec.ts (1)

4-5: Consider renaming selectors for clarity across both modes.

The selectors HSML_EDITOR and HTML_OUTPUT are accurate for compile mode but become semantically confusing in convert mode where HSML_EDITOR selects the HTML editor and HTML_OUTPUT selects the HSML output. Consider using mode-agnostic names.

Proposed rename for clarity
-const HSML_EDITOR = ':not(.editor-readonly) > .cm-editor .cm-content';
-const HTML_OUTPUT = '.editor-readonly .cm-editor .cm-content';
+const EDITABLE_EDITOR = ':not(.editor-readonly) > .cm-editor .cm-content';
+const READONLY_OUTPUT = '.editor-readonly .cm-editor .cm-content';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/playground.spec.ts` around lines 4 - 5, The selector constants
HSML_EDITOR and HTML_OUTPUT are semantically confusing across modes; rename them
to mode-agnostic identifiers (e.g., EDITOR_SELECTOR and OUTPUT_SELECTOR or
INPUT_SELECTOR and OUTPUT_SELECTOR) and update all usages in
e2e/playground.spec.ts (references to HSML_EDITOR and HTML_OUTPUT) so tests read
the editor vs output independent of compile/convert mode; ensure the values
remain the same (':not(.editor-readonly) > .cm-editor .cm-content' and
'.editor-readonly .cm-editor .cm-content') and update any comments or variable
docs accordingly.
src/composables/useEditorState.ts (1)

134-149: Clear pending timers when switching modes to prevent stale callbacks.

When switching modes, the pending debounced timer from the previous mode could still fire. Although the mode guard checks inside the timeout callbacks provide protection, clearing the timer explicitly is cleaner and prevents unnecessary timer execution.

Proposed fix
 // Trigger on mode switch — carry over the output as the new input
 watch(conversionMode, (mode) => {
+  clearTimeout(compileTimer);
+  clearTimeout(convertTimer);
   if (mode === 'compile') {
     if (hsmlOutput.value) {
       hsmlSource.value = hsmlOutput.value;
     }
     compileSource();
     writeToHash('compile', hsmlSource.value);
   } else {
     if (htmlOutput.value) {
       htmlInput.value = htmlOutput.value;
     }
     convertSource();
     writeToHash('convert', htmlInput.value);
   }
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/composables/useEditorState.ts` around lines 134 - 149, The watch handler
for conversionMode should clear any pending debounced timers from the previous
mode before invoking the new mode's functions to avoid stale callbacks; update
the watch(conversionMode, ...) block to cancel the relevant timers (e.g.,
clearTimeout(compileTimerId) and/or clearTimeout(convertTimerId)) or call
provided cancel methods from the debounced wrappers, then proceed with the
existing logic (copy hsmlOutput→hsmlSource or htmlOutput→htmlInput, call
compileSource() or convertSource(), and writeToHash()). Ensure the timer
identifiers or cancel functions are the same ones used inside
compileSource/convertSource so you actually stop any scheduled callbacks before
switching modes.
src/components/HtmlEditor.vue (1)

27-35: Tab handler only supports indentation, not un-indentation.

The custom Tab handler inserts spaces but doesn't handle Shift+Tab for removing indentation. This is a minor UX gap but acceptable for a playground.

Optional: Add Shift+Tab support
   const tabSize = keymap.of([
     {
       key: 'Tab',
       run: (view) => {
         view.dispatch(view.state.replaceSelection('  '));
         return true;
       },
     },
+    {
+      key: 'Shift-Tab',
+      run: (view) => {
+        const { from } = view.state.selection.main;
+        const line = view.state.doc.lineAt(from);
+        const lineText = line.text;
+        if (lineText.startsWith('  ')) {
+          view.dispatch({
+            changes: { from: line.from, to: line.from + 2, insert: '' },
+          });
+        }
+        return true;
+      },
+    },
   ]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/HtmlEditor.vue` around lines 27 - 35, The Tab keymap (tabSize)
only inserts two spaces and doesn't handle un-indentation for Shift+Tab; update
the keymap.of array to add a second binding for 'Shift-Tab' that inspects the
current selection/lines in the editor (using the view and view.state), and for
each selected line removes the leading two spaces (or a leading tab) where
present by dispatching an appropriate transaction via view.dispatch—modify the
existing tab handler logic name/tabSize and add a complementary 'Shift-Tab' run
handler that returns true after performing the un-indent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tsconfig.e2e.json`:
- Around line 1-14: Add "composite": true to the tsconfig's compilerOptions so
this config can be used as a project reference; update the tsconfig.e2e.json's
compilerOptions object to include composite: true (alongside
target/module/moduleResolution/etc.) to satisfy the root tsconfig's project
reference and enable TypeScript incremental/build info handling.

---

Nitpick comments:
In `@e2e/playground.spec.ts`:
- Around line 4-5: The selector constants HSML_EDITOR and HTML_OUTPUT are
semantically confusing across modes; rename them to mode-agnostic identifiers
(e.g., EDITOR_SELECTOR and OUTPUT_SELECTOR or INPUT_SELECTOR and
OUTPUT_SELECTOR) and update all usages in e2e/playground.spec.ts (references to
HSML_EDITOR and HTML_OUTPUT) so tests read the editor vs output independent of
compile/convert mode; ensure the values remain the same (':not(.editor-readonly)
> .cm-editor .cm-content' and '.editor-readonly .cm-editor .cm-content') and
update any comments or variable docs accordingly.

In `@src/components/HtmlEditor.vue`:
- Around line 27-35: The Tab keymap (tabSize) only inserts two spaces and
doesn't handle un-indentation for Shift+Tab; update the keymap.of array to add a
second binding for 'Shift-Tab' that inspects the current selection/lines in the
editor (using the view and view.state), and for each selected line removes the
leading two spaces (or a leading tab) where present by dispatching an
appropriate transaction via view.dispatch—modify the existing tab handler logic
name/tabSize and add a complementary 'Shift-Tab' run handler that returns true
after performing the un-indent.

In `@src/composables/useEditorState.ts`:
- Around line 134-149: The watch handler for conversionMode should clear any
pending debounced timers from the previous mode before invoking the new mode's
functions to avoid stale callbacks; update the watch(conversionMode, ...) block
to cancel the relevant timers (e.g., clearTimeout(compileTimerId) and/or
clearTimeout(convertTimerId)) or call provided cancel methods from the debounced
wrappers, then proceed with the existing logic (copy hsmlOutput→hsmlSource or
htmlOutput→htmlInput, call compileSource() or convertSource(), and
writeToHash()). Ensure the timer identifiers or cancel functions are the same
ones used inside compileSource/convertSource so you actually stop any scheduled
callbacks before switching modes.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f79e1dc8-58e5-4eae-89eb-a3a2f2e20b3c

📥 Commits

Reviewing files that changed from the base of the PR and between 1ae927b and cef5b4d.

📒 Files selected for processing (8)
  • e2e/playground.spec.ts
  • src/App.vue
  • src/components/HsmlOutput.vue
  • src/components/HtmlEditor.vue
  • src/components/SidebarPanel.vue
  • src/composables/useEditorState.ts
  • tsconfig.e2e.json
  • tsconfig.json

Comment thread tsconfig.e2e.json

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/components/HtmlEditor.vue (1)

27-47: Extract shared indent keymap to avoid drift between editors.

Line 27-Line 47 duplicates the same keymap logic used in src/components/HsmlEditor.vue. A shared helper will keep behavior aligned and reduce maintenance overhead.

♻️ Suggested refactor
--- a/src/components/HtmlEditor.vue
+++ b/src/components/HtmlEditor.vue
@@
 import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
 import { useEditorState } from '../composables/useEditorState';
 import { useTheme } from '../composables/useTheme';
+import { twoSpaceIndentKeymap } from '../codemirror/twoSpaceIndentKeymap';
@@
-  const indentKeymap = keymap.of([
-    {
-      key: 'Tab',
-      run: (view) => {
-        view.dispatch(view.state.replaceSelection('  '));
-        return true;
-      },
-    },
-    {
-      key: 'Shift-Tab',
-      run: (view) => {
-        const { state } = view;
-        const line = state.doc.lineAt(state.selection.main.head);
-        if (line.text.startsWith('  ')) {
-          view.dispatch({ changes: { from: line.from, to: line.from + 2 } });
-          return true;
-        }
-        return false;
-      },
-    },
-  ]);
@@
-        indentKeymap,
+        twoSpaceIndentKeymap,
// src/codemirror/twoSpaceIndentKeymap.ts
import { keymap } from '@codemirror/view';

export const twoSpaceIndentKeymap = keymap.of([
  {
    key: 'Tab',
    run: (view) => {
      view.dispatch(view.state.replaceSelection('  '));
      return true;
    },
  },
  {
    key: 'Shift-Tab',
    run: (view) => {
      const line = view.state.doc.lineAt(view.state.selection.main.head);
      if (!line.text.startsWith('  ')) return false;
      view.dispatch({ changes: { from: line.from, to: line.from + 2 } });
      return true;
    },
  },
]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/HtmlEditor.vue` around lines 27 - 47, The indentKeymap block
in HtmlEditor.vue duplicates the same Tab/Shift-Tab logic used in
HsmlEditor.vue; extract that shared keymap into a single exported helper (e.g.,
twoSpaceIndentKeymap) and import it into both components, replacing the local
indentKeymap definition; ensure the helper exports the keymap.of([...]) with the
same run handlers (using view.state.replaceSelection and
view.state.doc.lineAt/view.dispatch) and update HtmlEditor.vue and
HsmlEditor.vue to import and use twoSpaceIndentKeymap instead of their inline
definitions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/composables/useEditorState.ts`:
- Around line 139-147: The mode-handoff currently only assigns when outputs are
truthy, which loses valid empty-string outputs; update the handoff logic around
hsmlOutput -> hsmlSource and htmlOutput -> htmlInput (and then call
compileSource/writeToHash as before) to assign when the output is present even
if empty — e.g., check for null/undefined rather than truthiness
(hsmlOutput.value !== undefined && hsmlOutput.value !== null) or simply always
assign the .value so empty strings are carried through.

---

Nitpick comments:
In `@src/components/HtmlEditor.vue`:
- Around line 27-47: The indentKeymap block in HtmlEditor.vue duplicates the
same Tab/Shift-Tab logic used in HsmlEditor.vue; extract that shared keymap into
a single exported helper (e.g., twoSpaceIndentKeymap) and import it into both
components, replacing the local indentKeymap definition; ensure the helper
exports the keymap.of([...]) with the same run handlers (using
view.state.replaceSelection and view.state.doc.lineAt/view.dispatch) and update
HtmlEditor.vue and HsmlEditor.vue to import and use twoSpaceIndentKeymap instead
of their inline definitions.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 81888843-2eef-45f6-86bc-25b9548b058c

📥 Commits

Reviewing files that changed from the base of the PR and between cef5b4d and 81ae9d6.

📒 Files selected for processing (5)
  • e2e/playground.spec.ts
  • src/components/HsmlEditor.vue
  • src/components/HtmlEditor.vue
  • src/composables/useEditorState.ts
  • tsconfig.e2e.json
✅ Files skipped from review due to trivial changes (1)
  • tsconfig.e2e.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • e2e/playground.spec.ts

Comment thread src/composables/useEditorState.ts
@Shinigami92
Shinigami92 added this pull request to the merge queue Apr 10, 2026
Merged via the queue into main with commit 85f04dd Apr 10, 2026
6 checks passed
@Shinigami92
Shinigami92 deleted the feat-convert-html branch April 10, 2026 20:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c: feature Request for new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant