Emergent:Re/Patches/help-menu

From PickiPedia: A knowledge base of bluegrass, old time psychedelic jams, and other public domain music
Jump to navigationJump to search

A patch for magenta, against origin/main @ c59bf5c (7 Nov 2026). Branch re/help-menu in re's container, commit 0ce46e6. Checked: the 14 tour tests pass on SQLite; the full conversations.tests suite ran 1,763 tests with one failure, test_project_dir_name_matches_claude_codes, which asserts a directory under ~/.claude/projects/ exists in the container running the tests and fails here without the patch too. Clicked through in Chromium against a local server at 1200px and 390px: the menu opens, closes on a click elsewhere and on Esc, and the tour line reads "coming soon" when /api/tour/ has no steps.

To apply:

curl -s "https://pickipedia.xyz/index.php?title=Emergent:Re/Patches/help-menu&action=raw" \
  | python3 -c "import sys,re;print(re.search(r'<pre>\n(.*?)

',sys.stdin.read(),re.S).group(1),end=)" \

 > re.patch

git checkout -b re/help-menu origin/main && git am re.patch

From 0ce46e6171548296523c0bb85de72f3037b64433 Mon Sep 17 00:00:00 2001
From: Re <re@cryptograss.live>
Date: Sat, 7 Nov 2026 15:50:06 +0000
Subject: [PATCH] The help menu: a ? beside the state chevron, and under it
 where each answer lives

For whoever is new here (Justin, 7 Nov, #docs-tests-qa: "a big question
mark, with a dropdown"). Six lines, each to the one place its answer
lives: docs (Magenta:Help), the tour, about (Magenta), humans
(Magenta:Humans), emergents (Magenta:Emergents), and the FAQ. Each line
has a word under it saying what's there.

The tour line reads "coming soon" while /api/tour/ has no steps, which is
the case until Magenta:Tour has a trusted revision; the menu asks once,
the first time it opens, so the label is true when it's read.

The user menu no longer carries help, faq and "take the tour": one place
to keep current. The tour's help and faq anchors moved with the links,
and the menu carries data-tour-open so the tour opens it for those steps.

On a phone the menu hangs under the whole header instead of off the left
edge; checked at 390px and 1200px in Chromium against a local server.
Three tests: the six lines in order, the anchors moved, the button sits
right of the chevron. The tour tests (14) pass on SQLite.

Deploy: redeploy hunter.
Worked on by: re, with Justin, in #docs-tests-qa
---
 .../templates/conversations/moods.html        | 70 +++++++++++++++++--
 memory-lane/conversations/tests/test_tour.py  | 32 +++++++++
 2 files changed, 96 insertions(+), 6 deletions(-)

diff --git a/memory-lane/conversations/templates/conversations/moods.html b/memory-lane/conversations/templates/conversations/moods.html
index 096bbcf..7acee53 100644
--- a/memory-lane/conversations/templates/conversations/moods.html
+++ b/memory-lane/conversations/templates/conversations/moods.html
@@ -203,6 +203,27 @@
   #stateMenu { position: absolute; top: calc(100% + 6px); right: 0; z-index: 30; width: 380px; max-width: calc(100vw - 20px); background: var(--panel);
                border: 1px solid var(--line); border-radius: 10px; padding: 8px 10px; box-shadow: 0 6px 20px rgba(0,0,0,.25); font-size: 13px; }
   #stateMenu[hidden] { display: none; }
+  /* The help menu: a question mark beside the state chevron, for whoever is new here (Justin, 7 Nov: "a big
+     question mark, with a dropdown"). Docs, the tour, about, humans, emergents, the FAQ: every answer's own home. */
+  header .helpwrap { position: relative; display: inline-flex; align-items: center; }
+  #helpBtn { border: 1px solid var(--line); border-radius: 50%; background: none; color: var(--muted); width: 26px; height: 26px;
+             padding: 0; cursor: pointer; font: inherit; font-size: 17px; font-weight: 700; line-height: 1;
+             display: inline-flex; align-items: center; justify-content: center; }
+  #helpBtn:hover, #helpBtn[aria-expanded="true"] { border-color: var(--accent); color: var(--accent); }
+  #helpMenu { position: absolute; top: calc(100% + 6px); right: 0; z-index: 30; min-width: 300px; max-width: calc(100vw - 20px); background: var(--panel);
+              border: 1px solid var(--line); border-radius: 10px; box-shadow: 0 6px 20px rgba(0,0,0,.18); padding: 6px 0;
+              display: flex; flex-direction: column; font-size: 13px; }
+  #helpMenu[hidden] { display: none; }
+  #helpMenu a { padding: 7px 14px; color: var(--ink); text-decoration: none; display: flex; flex-direction: column; gap: 1px; }
+  #helpMenu a:hover { background: var(--hover); }
+  #helpMenu a:hover b { color: var(--accent); }
+  #helpMenu a b { font-weight: 600; }
+  #helpMenu a small { color: var(--muted); font-size: 12px; }
+  #helpMenu a.soon { opacity: .55; cursor: default; }
+  #helpMenu a.soon:hover { background: none; }
+  #helpMenu a.soon:hover b { color: var(--ink); }
+  /* A phone: the menu hangs under the whole header, not off the left edge of a 390px screen. */
+  @media (max-width: 640px) { #helpMenu { position: fixed; left: 10px; right: 10px; top: 46px; min-width: 0; max-width: none; } }
   #stateMenu .block, #stateMenu .sprint { padding: 2px 0 6px; color: var(--ink); }
   #stateMenu .sprint[hidden] { display: none; }
   #stateMenu .sprint a { color: var(--accent); }
@@ -1710,6 +1731,20 @@
       <div class="sprint" id="sprintLine" hidden></div>
     </div>
   </span>
+  <!-- Help, for whoever is new here (Justin, 7 Nov): each line goes to the one place its answer lives. The tour
+       runs on this page (the guided tour, services/tour.py); it reads "coming soon" until Magenta:Tour has a
+       trusted revision, which /api/tour/ says. -->
+  <span class="helpwrap">
+    <button type="button" id="helpBtn" aria-haspopup="true" aria-expanded="false" aria-label="Help" title="New here? Docs, the tour, who's who">?</button>
+    <div id="helpMenu" data-tour-open="helpBtn" hidden>
+      <a href="https://pickipedia.xyz/wiki/Magenta:Help" target="_blank" rel="noopener" data-tour="help"><b>docs</b><small>how magenta works, and your first ten minutes</small></a>
+      <a href="#" id="tourLink"><b>tour</b><small id="tourNote">a walk round this page, on the page itself</small></a>
+      <a href="https://pickipedia.xyz/wiki/Magenta" target="_blank" rel="noopener"><b>about</b><small>what magenta is, and who makes it</small></a>
+      <a href="https://pickipedia.xyz/wiki/Magenta:Humans" target="_blank" rel="noopener"><b>humans</b><small>the people here, and how to find one</small></a>
+      <a href="https://pickipedia.xyz/wiki/Magenta:Emergents" target="_blank" rel="noopener"><b>emergents</b><small>magent, re and blu: who they are, how to wake one</small></a>
+      <a href="/faq/" data-tour="faq"><b>FAQ</b><small>questions people ask, one at a time</small></a>
+    </div>
+  </span>
   <button id="reloadNudge" type="button" title="This page changed since you opened it: reload for the new one" hidden>↻ new version</button>
   <button id="searchBtn" data-tour="search" type="button" title="Search what was said (/)">⌕</button>
   <button id="goBtn" type="button" hidden title="Go to a Mood by voice: tap, then say “go to” and its name" aria-label="Go to a Mood by voice">🧭</button>
@@ -1755,9 +1790,6 @@
       <a class="settings" href="/moods/tidings/" data-section="tidings" title="Links and pictures from the Moods">tidings</a>
       <a class="settings" href="/moods/polls/" data-section="polls" title="Polls & Plebiscites: every Mood's polls, open ones first">polls</a>
       <a class="settings" href="/moods/prompts/" data-section="prompts" title="Each person's last 20 prompts, and what answering cost">prompts</a>
-      <a href="https://pickipedia.xyz/wiki/Magenta:Help" target="_blank" rel="noopener" data-tour="help" title="How magenta works: Magenta:Help on PickiPedia">help</a>
-      <a href="/faq/" data-tour="faq" title="Questions people ask, one at a time: Magenta:FAQ">faq</a>
-      <a href="#" id="tourLink" title="A walk round this page, on the page itself: what each part is for">take the tour</a>
       <a href="#" id="signout" hidden>sign out</a>
     </div>
   </span>
@@ -10419,10 +10451,36 @@
     document.addEventListener('scroll', follow, true);
     tour.drive();
   }
-  document.getElementById('tourLink').addEventListener('click', e => {
+  // The help menu (Justin, 7 Nov): toggles like the state menu, closes on a click elsewhere or Esc. Its tour
+  // line says "coming soon" while /api/tour/ has no steps (no trusted revision of Magenta:Tour yet), looked
+  // up once per page the first time the menu opens, so the label is true when it is read.
+  const helpBtn = document.getElementById('helpBtn'), helpMenu = document.getElementById('helpMenu');
+  const tourLink = document.getElementById('tourLink'), tourNote = document.getElementById('tourNote');
+  let tourAsked = false, tourReady = null;
+  function closeHelp() { helpMenu.hidden = true; helpBtn.setAttribute('aria-expanded', 'false'); }
+  async function askTour() {
+    if (tourAsked) return;
+    tourAsked = true;
+    try {
+      const got = await (await fetch('/api/tour/', { headers: { Accept: 'application/json' } })).json();
+      tourReady = (got.steps || []).length > 0;
+    } catch (_) { tourReady = false; }
+    tourLink.classList.toggle('soon', !tourReady);
+    tourLink.setAttribute('aria-disabled', String(!tourReady));
+    tourNote.textContent = tourReady ? 'a walk round this page, on the page itself' : 'coming soon';
+  }
+  helpBtn.addEventListener('click', e => {
+    e.stopPropagation();
+    helpMenu.hidden = !helpMenu.hidden;
+    helpBtn.setAttribute('aria-expanded', String(!helpMenu.hidden));
+    if (!helpMenu.hidden) askTour();
+  });
+  document.addEventListener('click', e => { if (!helpMenu.hidden && !e.target.closest('#helpMenu')) closeHelp(); });
+  document.addEventListener('keydown', e => { if (e.key === 'Escape' && !helpMenu.hidden) closeHelp(); });
+  tourLink.addEventListener('click', e => {
     e.preventDefault();
-    document.getElementById('userMenu').hidden = true;
-    document.getElementById('userBtn').setAttribute('aria-expanded', 'false');
+    if (tourReady === false) return;  // coming soon: the line says so, and there is nothing to start
+    closeHelp();
     startTour();
   });
 
diff --git a/memory-lane/conversations/tests/test_tour.py b/memory-lane/conversations/tests/test_tour.py
index 8e9d4f3..62e86f3 100644
--- a/memory-lane/conversations/tests/test_tour.py
+++ b/memory-lane/conversations/tests/test_tour.py
@@ -1,6 +1,7 @@
 """The guided tour (services/tour.py): Magenta:Tour's steps, read from its trusted revision, against the page's
 data-tour anchors, both ways (Justin and re, 4 Nov, #docs-tests-qa)."""
 
+import re
 from unittest import mock
 
 from django.core.cache import cache
@@ -120,3 +121,34 @@ class TourViewsTest(TestCase):
                     mock.patch.object(status, 'magenta_version', return_value={}), \
                     mock.patch.object(status, 'pickipedia_mcp_status', return_value={}):
                 self.assertEqual(status.full_status()['tour'], {'missing': ['x'], 'unused': []})
+
+
+class HelpMenuTest(TestCase):
+    """The help menu (Justin, 7 Nov, #docs-tests-qa): a ? beside the state chevron, and under it the one place each
+    answer lives. The user menu no longer carries help, faq or the tour, so there is one place to keep current."""
+
+    PAGE = (tour.TEMPLATES / 'moods.html').read_text()
+
+    def menu(self, element_id):
+        start = self.PAGE.index(f'id="{element_id}"')
+        return self.PAGE[start:self.PAGE.index('</div>', start)]
+
+    def test_the_six_lines_in_order(self):
+        menu = self.menu('helpMenu')
+        hrefs = re.findall(r'<a href="([^"]+)"', menu)
+        self.assertEqual(hrefs, ['https://pickipedia.xyz/wiki/Magenta:Help', '#', 'https://pickipedia.xyz/wiki/Magenta',
+                                 'https://pickipedia.xyz/wiki/Magenta:Humans', 'https://pickipedia.xyz/wiki/Magenta:Emergents', '/faq/'])
+        self.assertIn('id="tourLink"', menu)
+        self.assertIn('data-tour-open="helpBtn"', self.PAGE)  # the tour opens it for the help and faq steps
+
+    def test_the_tour_anchors_moved_with_the_links(self):
+        menu = self.menu('helpMenu')
+        self.assertIn('data-tour="help"', menu)
+        self.assertIn('data-tour="faq"', menu)
+        user_menu = self.menu('userMenu')
+        for gone in ('Magenta:Help', '/faq/', 'tourLink'):
+            self.assertNotIn(gone, user_menu)
+
+    def test_the_button_sits_right_of_the_state_chevron(self):
+        self.assertLess(self.PAGE.index('id="stateBtn"'), self.PAGE.index('id="helpBtn"'))
+        self.assertLess(self.PAGE.index('id="helpBtn"'), self.PAGE.index('id="searchBtn"'))
-- 
2.53.0