Skip to content

Tidy index#409

Merged
tracygardner merged 8 commits intoflipcomputing:mainfrom
lawsie:tidy-index
Mar 18, 2026
Merged

Tidy index#409
tracygardner merged 8 commits intoflipcomputing:mainfrom
lawsie:tidy-index

Conversation

@lawsie
Copy link
Contributor

@lawsie lawsie commented Mar 18, 2026

Summary

Various small changes to tidy the index.html page
Claude Sonnet 4.6 was asked to audit the page for issues, then issues were fixed manually or with advice from AI.

Changes

  • Remove lots of commented out code
  • Fix link colour being unreadable in modal in dark/contrast themes
  • Fixed issue where escape key wasn’t closing menus properly
  • Removed width/height attributes from SVGs which were overriding CSS sizing specified
  • SVG icons in the nav now use brand colour as defined by theme
  • SVG icons in the gizmo palette now change with theme, rather than being !important in dark themes
  • Moved JavaScript for menu into main/menu.js

Summary by CodeRabbit

  • New Features

    • Added an accessible flyout menu with full keyboard navigation.
    • Introduced an About modal dialog with improved focus trapping and restoration.
  • Improvements

    • Icons and emoji fallbacks now inherit theme color for consistent theming.
    • Reduced inline scripting by moving menu logic to external scripts.
    • Loading heading adjusted for better accessibility; icon sizing and dark/contrast visibility improved.

@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

📝 Walkthrough

Walkthrough

Extracts a large inline menu script into a new accessible module, replaces hard-coded SVG fills with currentColor and CSS variables, standardizes inline SVGs/icons, adjusts accessibility markup, and updates CSS to ensure consistent icon theming and About modal link visibility.

Changes

Cohort / File(s) Summary
Menu System & Behavior
index.html, main/menu.js
Moves inline menu script into main/menu.js; introduces AccessibleFlyoutMenu with keyboard navigation, focus management, submenu handling, global outside-click/Escape handling, and an exported menuFlyout instance. Wires hub item and About modal interactions to the public API.
Icon/SVG Standardization
index.html (multiple inline SVGs), index.html (gizmo & controls sections)
Replaces many hard-coded SVG fills and image fallbacks with standardized inline SVGs using currentColor; adds inline sizing styles and removes legacy fallbacks. Updates ARIA/tabindex attributes to align with externalized scripting.
Stylesheet Adjustments
style.css
Adds .icon color rule and .gizmo-button path fill using CSS variables, ensures About modal links are visible in dark/contrast themes, and constrains #projectName layout for consistent icon/text alignment.
Accessibility & Semantics
index.html, style.css
Changes loading heading from h1 to h2, refines ARIA labels/tabindex values, and updates comments/dup CSS references after moving scripts out of HTML.

Sequence Diagram

sequenceDiagram
    participant User
    participant DOM as index.html (DOM)
    participant Menu as AccessibleFlyoutMenu (main/menu.js)
    participant Focus as Focus Manager
    participant Modal as About Modal

    User->>DOM: Click menu toggle
    DOM->>Menu: toggleMainMenu()
    Menu->>Menu: openMainMenu()
    Menu->>Focus: focusFirstMenuItem()

    User->>Menu: Arrow/Enter keys
    Menu->>Focus: focusMenuItem / showSubmenu()
    Menu->>User: focus moves to item / submenu

    User->>Menu: Select hub item
    Menu->>DOM: open hub URL
    Menu->>Menu: closeAllMenus()

    User->>DOM: Click About
    DOM->>Modal: openAbout()
    Modal->>Focus: trapFocus()
    User->>Modal: Escape
    Modal->>Modal: closeInfoModal()
    Modal->>Focus: restoreFocus()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Remove inline styles #408: Modifies icon markup and styling (removing inline icon styles and standardizing icon sizing/color), closely related to this PR's SVG and CSS standardization.

Poem

🐰 I hopped from inline script to file,
currentColor makes icons smile,
Arrow keys guide every flight,
submenus open, focus feels right —
a tiny rabbit cheers the style!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Tidy index' is vague and generic, using non-descriptive language that doesn't convey the specific changes made in the pull request. Use a more specific title that highlights the main change, such as 'Refactor menu functionality and improve theme consistency in index.html' or 'Extract menu logic to external module and fix theme colors'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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

@lawsie lawsie marked this pull request as draft March 18, 2026 09:59
Copy link

@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: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@index.html`:
- Around line 716-719: The gizmo toolbar contains an extra closing </button>
causing invalid markup: remove the duplicate closing tag that follows the button
with id="scaleButton" and class="gizmo-button" (the button that contains the
SVG) so that there is exactly one matching opening <button ...> and one closing
</button> for the scaleButton element; ensure no other surrounding elements rely
on that stray tag.

In `@main/menu.js`:
- Around line 293-301: previouslyFocused is capturing the soon-hidden
`#about-menu-item` and closeInfoModal.focus() targets a non-interactive <span>,
which can leave keyboard focus outside the dialog; update the open handlers
(openAbout and the similar handler at 305-309) to set previouslyFocused to a
real interactive element (e.g., menuBtn) and focus an actual focusable control
inside the modal (replace focusing the <span> closeInfoModal with focusing a
real button or add tabindex/role to the close control), ensure infoModal
receives aria-hidden/aria-modal attributes as needed, and keep menuDropdown
hidden and menuBtn aria-expanded="false" as before so keyboard trapping and
Escape handling reliably engage.
- Around line 112-120: The closeAllSubmenus logic uses submenu.hidden but CSS
:focus-within/:hover can keep a submenu visible; update closeAllSubmenus (and
where you close a single submenu) to not only set submenu.hidden = true and
item.setAttribute('aria-expanded','false') but also remove focus from the parent
so CSS :focus-within doesn't re-open it (e.g., call item.blur() or move focus to
a neutral element like document.body) and ensure currentOpenSubmenu is cleared;
target the closeAllSubmenus method, the this.menuItems loop, and the code paths
that set currentOpenSubmenu to null.
- Around line 61-68: The submenu click listener allows the click to bubble to
the parent .menu-item handler which then re-opens the submenu; update the
submenu click handler (inside submenuItems.forEach) to stop the event from
bubbling by calling e.stopPropagation() (and optionally e.preventDefault() if
needed) before calling this.closeAllMenus(), so the parent click handler doesn't
receive the event and re-toggle the submenu.
- Around line 234-258: In handleSubmenuKeydown, each switch case that declares
const (nextIndex, prevIndex, submenu) must be wrapped in its own block to
satisfy noSwitchDeclarations; for the 'ArrowDown', 'ArrowUp', and 'ArrowLeft'
cases, add { ... } around the existing case body (keeping e.preventDefault(),
e.stopPropagation(), the const declarations, focus()/hidden/aria-expanded
changes, and the break) so the const bindings are lexically scoped and the
linter error is resolved.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8f677ec6-1137-4b0d-9aec-6397a2ec2b7b

📥 Commits

Reviewing files that changed from the base of the PR and between 54cdab3 and 99eb3ce.

📒 Files selected for processing (3)
  • index.html
  • main/menu.js
  • style.css

Comment on lines +112 to +120
closeAllSubmenus() {
this.menuItems.forEach(item => {
const submenu = item.querySelector('.submenu');
if (submenu) {
submenu.hidden = true;
item.setAttribute('aria-expanded', 'false');
}
});
this.currentOpenSubmenu = null;
Copy link

@coderabbitai coderabbitai bot Mar 18, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

hidden is not authoritative with the current submenu CSS.

These close paths rely on submenu.hidden = true, but style.css still forces submenus open under .menu-item:hover and .menu-item:focus-within. After focus returns to the parent item, the submenu can stay visible while the JS state says it is closed.

.submenu[hidden] {
  display: none !important;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@main/menu.js` around lines 112 - 120, The closeAllSubmenus logic uses
submenu.hidden but CSS :focus-within/:hover can keep a submenu visible; update
closeAllSubmenus (and where you close a single submenu) to not only set
submenu.hidden = true and item.setAttribute('aria-expanded','false') but also
remove focus from the parent so CSS :focus-within doesn't re-open it (e.g., call
item.blur() or move focus to a neutral element like document.body) and ensure
currentOpenSubmenu is cleared; target the closeAllSubmenus method, the
this.menuItems loop, and the code paths that set currentOpenSubmenu to null.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this needs to be a separate issue.

Copy link

Choose a reason for hiding this comment

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

@lawsie, understood! Would you like me to open a GitHub issue to track this? I can create one with the relevant details about the submenu.hidden / CSS :hover+:focus-within conflict so it doesn't get lost.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

gh issue create \
  --repo flipcomputing/flock \
  --title "Fix submenu.hidden / CSS :hover+:focus-within conflict in menu" \
  --body "## Problem

In \`main/menu.js\`, the \`closeAllSubmenus()\` method closes submenus by setting \`submenu.hidden = true\` and \`item.setAttribute('aria-expanded', 'false')\`. However, \`style.css\` still has CSS rules that force submenus open via \`.menu-item:hover\` and \`.menu-item:focus-within\`. After focus returns to the parent item, the submenu can remain visible even though the JS state says it is closed.

## Suggested Fix

One or more of the following approaches should be applied:

1. Add a CSS rule to ensure \`[hidden]\` always wins:
   \`\`\`css
   .submenu[hidden] {
     display: none !important;
   }
   \`\`\`
2. In \`closeAllSubmenus()\` (and any other single-submenu close paths), call \`item.blur()\` or move focus to a neutral element (e.g. \`document.body\`) so \`:focus-within\` does not re-open the submenu.
3. Clear \`currentOpenSubmenu\` in all relevant close paths.

## Affected Files

- \`main/menu.js\` — \`closeAllSubmenus()\` method (around lines 112–120)
- \`style.css\` — \`.menu-item:hover .submenu\` / \`.menu-item:focus-within .submenu\` rules

## References

- PR `#409`: https://github.com/flipcomputing/flock/pull/409
- Review comment: https://github.com/flipcomputing/flock/pull/409#discussion_r2952243123

Raised by `@lawsie`." \
  --assignee lawsie

Length of output: 41


🏁 Script executed:

gh issue create \
  --repo flipcomputing/flock \
  --title "Fix submenu.hidden / CSS :hover+:focus-within conflict in menu" \
  --body "## Problem

In \`main/menu.js\`, the \`closeAllSubmenus()\` method closes submenus by setting \`submenu.hidden = true\` and \`item.setAttribute('aria-expanded', 'false')\`. However, \`style.css\` still has CSS rules that force submenus open via \`.menu-item:hover\` and \`.menu-item:focus-within\`. After focus returns to the parent item, the submenu can remain visible even though the JS state says it is closed.

## Suggested Fix

One or more of the following approaches should be applied:

1. Add a CSS rule to ensure \`[hidden]\` always wins:
   \`\`\`css
   .submenu[hidden] {
     display: none !important;
   }
   \`\`\`
2. In \`closeAllSubmenus()\` (and any other single-submenu close paths), call \`item.blur()\` or move focus to a neutral element (e.g. \`document.body\`) so \`:focus-within\` does not re-open the submenu.
3. Clear \`currentOpenSubmenu\` in all relevant close paths.

## Affected Files

- \`main/menu.js\` — \`closeAllSubmenus()\` method (around lines 112–120)
- \`style.css\` — \`.menu-item:hover .submenu\` / \`.menu-item:focus-within .submenu\` rules

## References

- PR `#409`: https://github.com/flipcomputing/flock/pull/409
- Review comment: https://github.com/flipcomputing/flock/pull/409#discussion_r2952243123

Raised by `@lawsie`."

Length of output: 49


@lawsie, the issue has been created: #412

It captures the submenu.hidden / CSS :hover+:focus-within conflict, the affected files (main/menu.js and style.css), and the suggested fix options for when you're ready to address it.

Comment on lines +293 to +301
openAbout.addEventListener("click", (e) => {
e.preventDefault();
previouslyFocused = document.activeElement;
infoModal.classList.remove("hidden");
menuDropdown.classList.add("hidden");
menuBtn.setAttribute("aria-expanded", "false");

// Focus the close button in the modal
setTimeout(() => closeInfoModal.focus(), 0);
Copy link

@coderabbitai coderabbitai bot Mar 18, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Move focus to a real target when opening the About modal.

This can capture the soon-hidden #about-menu-item as previouslyFocused, and closeInfoModal.focus() targets a <span> in index.html. In the keyboard path, focus can stay outside the dialog, so the Tab trap and Escape handling do not reliably engage.

Suggested fix
 openAbout.addEventListener("click", (e) => {
     e.preventDefault();
-    previouslyFocused = document.activeElement;
+    previouslyFocused =
+        menuDropdown.contains(document.activeElement) ? menuBtn : document.activeElement;
     infoModal.classList.remove("hidden");
     menuDropdown.classList.add("hidden");
     menuBtn.setAttribute("aria-expanded", "false");

-    // Focus the close button in the modal
-    setTimeout(() => closeInfoModal.focus(), 0);
+    const modalFocusTarget =
+        infoModal.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])') ??
+        infoModal.querySelector(".modal-content");
+    setTimeout(() => modalFocusTarget?.focus(), 0);
 });

 closeInfoModal.addEventListener("click", () => {
     infoModal.classList.add("hidden");
-    if (previouslyFocused) {
-    previouslyFocused.focus();
-    previouslyFocused = null;
-    }
+    if (previouslyFocused && document.contains(previouslyFocused)) {
+        previouslyFocused.focus();
+    } else {
+        menuBtn.focus();
+    }
+    previouslyFocused = null;
 });

Also applies to: 305-309

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

In `@main/menu.js` around lines 293 - 301, previouslyFocused is capturing the
soon-hidden `#about-menu-item` and closeInfoModal.focus() targets a
non-interactive <span>, which can leave keyboard focus outside the dialog;
update the open handlers (openAbout and the similar handler at 305-309) to set
previouslyFocused to a real interactive element (e.g., menuBtn) and focus an
actual focusable control inside the modal (replace focusing the <span>
closeInfoModal with focusing a real button or add tabindex/role to the close
control), ensure infoModal receives aria-hidden/aria-modal attributes as needed,
and keep menuDropdown hidden and menuBtn aria-expanded="false" as before so
keyboard trapping and Escape handling reliably engage.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This should be a separate issue

Copy link

Choose a reason for hiding this comment

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

@lawsie, understood! Would you like me to open a GitHub issue to track this focus management concern? I'll include a reference back to this PR and comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

gh issue create \
  --repo flipcomputing/flock \
  --title "Fix focus management when opening the About modal" \
  --assignee lawsie \
  --body "## Summary

When the About modal is opened via the menu, focus management has two related problems:

1. \`previouslyFocused\` captures \`#about-menu-item\`, which is inside the menu that becomes hidden immediately after — so returning focus on close may target a hidden element.
2. \`closeInfoModal.focus()\` targets a \`<span>\`, which is non-interactive by default, meaning keyboard focus may land outside the dialog. This means the Tab trap and Escape handler do not reliably engage for keyboard users.

## Impact

Keyboard and assistive-technology users may lose focus context when opening or closing the About modal.

## References

- PR: https://github.com/flipcomputing/flock/pull/409
- Review comment: https://github.com/flipcomputing/flock/pull/409#discussion_r2952243133

Requested by `@lawsie`.
"

Length of output: 41


🏁 Script executed:

gh issue create \
  --repo flipcomputing/flock \
  --title "Fix focus management when opening the About modal" \
  --body "## Summary

When the About modal is opened via the menu, focus management has two related problems:

1. \`previouslyFocused\` captures \`#about-menu-item\`, which is inside the menu that becomes hidden immediately after — so returning focus on close may target a hidden element.
2. \`closeInfoModal.focus()\` targets a \`<span>\`, which is non-interactive by default, meaning keyboard focus may land outside the dialog. This means the Tab trap and Escape handler do not reliably engage for keyboard users.

## Impact

Keyboard and assistive-technology users may lose focus context when opening or closing the About modal.

## References

- PR: https://github.com/flipcomputing/flock/pull/409
- Review comment: https://github.com/flipcomputing/flock/pull/409#discussion_r2952243133

Requested by `@lawsie`.
"

Length of output: 49


@lawsie, I've opened a GitHub issue to track this: #411

@lawsie
Copy link
Contributor Author

lawsie commented Mar 18, 2026

OK I think this one is alright now, it wasn't previously loading the new JS file on deployment as it wasn't labelled as a module but now fixed. The bot identified a couple of issues (#411 and #412) that I think are separate things - they were flagged because the JS was moved, but weren't things I touched.

@lawsie lawsie marked this pull request as ready for review March 18, 2026 12:00
Copy link

@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 (2)
main/menu.js (1)

75-79: Consolidate duplicate outside-click menu closing logic.

Menu-close-on-outside-click is implemented twice (Line 75 and Line 338 paths). Keeping both increases drift risk and double execution side effects.

♻️ Suggested simplification
 window.addEventListener("click", (e) => {
-    if (!menuBtn.contains(e.target) && !menuDropdown.contains(e.target)) {
-        menuFlyout.closeAllMenus();
-    }
-
     if (e.target === infoModal) {
         closeInfoModal.click();
     }
 });

Also applies to: 337-341

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

In `@main/menu.js` around lines 75 - 79, There are two identical outside-click
handlers that both call closeAllMenus (one currently added via
document.addEventListener using this.menuButton and this.menuDropdown and a
duplicate elsewhere), which can cause double execution; consolidate by creating
a single method (e.g., attachOutsideClickHandler or initOutsideClickHandler) on
the Menu class that installs a single document click listener that checks
!this.menuButton.contains(e.target) && !this.menuDropdown.contains(e.target) and
calls closeAllMenus, ensure callers use that method instead of adding their own
listener, guard against adding the listener twice (track a boolean or store the
handler) and remove it in any teardown/destroy logic to prevent leaks.
index.html (1)

605-606: Remove fixed SVG dimensions on showShapesButton to keep CSS in control.

Line 605 still hard-codes width/height, which can override responsive button/icon sizing.

♻️ Suggested cleanup
-                <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="122.875px" height="122.648px" viewBox="0 0 122.875 122.648" enable-background="new 0 0 122.875 122.648" xml:space="preserve">
+                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.875 122.648" aria-hidden="true">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@index.html` around lines 605 - 606, The SVG inside the showShapesButton is
hard-coded with width="122.875px" and height="122.648px" which overrides CSS
sizing; remove those width and height attributes from the <svg> element (leave
the existing viewBox="0 0 122.875 122.648") so the button/icon can be sized via
CSS (e.g., font-size, width/height on the button or svg selector) and ensure no
other inline size attributes remain on the showShapesButton SVG.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@index.html`:
- Around line 605-606: The SVG inside the showShapesButton is hard-coded with
width="122.875px" and height="122.648px" which overrides CSS sizing; remove
those width and height attributes from the <svg> element (leave the existing
viewBox="0 0 122.875 122.648") so the button/icon can be sized via CSS (e.g.,
font-size, width/height on the button or svg selector) and ensure no other
inline size attributes remain on the showShapesButton SVG.

In `@main/menu.js`:
- Around line 75-79: There are two identical outside-click handlers that both
call closeAllMenus (one currently added via document.addEventListener using
this.menuButton and this.menuDropdown and a duplicate elsewhere), which can
cause double execution; consolidate by creating a single method (e.g.,
attachOutsideClickHandler or initOutsideClickHandler) on the Menu class that
installs a single document click listener that checks
!this.menuButton.contains(e.target) && !this.menuDropdown.contains(e.target) and
calls closeAllMenus, ensure callers use that method instead of adding their own
listener, guard against adding the listener twice (track a boolean or store the
handler) and remove it in any teardown/destroy logic to prevent leaks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 31743d23-1f26-49c8-b2d3-51ab23f2ac8b

📥 Commits

Reviewing files that changed from the base of the PR and between 99eb3ce and 1238dd2.

📒 Files selected for processing (2)
  • index.html
  • main/menu.js

@tracygardner tracygardner merged commit 7cbc380 into flipcomputing:main Mar 18, 2026
3 checks passed
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