Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 53 additions & 3 deletions board/_theme.py
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,13 @@ def mark(key):
background:var(--warn)}
a.go:hover{opacity:.9;text-decoration:none}
.mdsrc{font-size:.85em}
.board-source-link{display:inline-flex;align-items:center;justify-content:center;flex:none;width:44px;height:44px;border-radius:6px;color:var(--muted);text-decoration:none}
.board-source-link:hover{color:var(--accent);background:var(--btn)}
.board-source-link:focus-visible{outline:3px solid var(--accent);outline-offset:2px}
.board-source-link svg{width:18px;height:18px;pointer-events:none}
p:has(>.board-source-link){display:flex;align-items:center;gap:.5rem;max-width:none}
p>.board-source-link{order:1;margin-left:auto}
.board-section>summary>.board-source-link{margin-left:auto}
"""

# One organ chip colour per scheme, appended to the sheet so the family
Expand Down Expand Up @@ -1244,8 +1251,9 @@ def portable_prompt(payload):
def section_layout(page, summaries=None):
"""Compose an owner's rendered board into the shared disclosure layout.

Preserve the owner's HTML verbatim, including IDs and copy payloads. Only
major heading groups are wrapped; existing details and card headings are
Preserve owner IDs, destinations and copy payloads. Markdown source links
become accessible icons; major heading groups are wrapped, while existing
details and card headings are
left alone. Optional summaries map section IDs to owner-supplied count,
status and label fields. This helper never infers health from prose.
"""
Expand Down Expand Up @@ -1296,6 +1304,41 @@ def handle_endtag(self, tag):
break

tree = Tree(page)
# Normalize the existing source-link convention once for every consumer.
# Match the complete label, never arbitrary links to Markdown documents.
source_edits = []
for node in tree.nodes:
if node['tag'] != 'a' or not node['end'] or not node['attrs'].get('href'):
continue
label = _html.unescape(_re.sub('<[^>]*>', '', page[node['inner']:node['close']])).strip()
if label.lower() != 'markdown version':
continue
attrs = dict(node['attrs'])
attrs['class'] = (attrs.get('class', '') + ' board-source-link').strip()
attrs['title'] = attrs['aria-label'] = 'Markdown version'
attributes = ''.join(' ' + key + (f'="{_html.escape(value, quote=True)}"'
if value is not None else '') for key, value in attrs.items())
icon = ('<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" '
'stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" '
'aria-hidden="true" focusable="false">'
'<path d="M14 3H6a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V8Z"/>'
'<path d="M14 3v5h5M8 12h8M8 16h6"/></svg>')
start, end = node['start'], node['end']
parent = node['parent']
if parent and parent['tag'] == 'p':
# Page-level links share a utility row; its separator is redundant
# once the source link moves to the far right.
trailing = _re.match(r'\s*·\s*', page[end:parent['close']])
preceding = _re.search(r'\s*·\s*$', page[parent['inner']:start])
if trailing:
end += trailing.end()
elif preceding:
start = parent['inner'] + preceding.start()
source_edits.append((start, end, '<a' + attributes + '>' + icon + '</a>'))
for start, end, replacement in reversed(source_edits):
page = page[:start] + replacement + page[end:]
if source_edits:
tree = Tree(page)
summaries = summaries or {}

def classes(node):
Expand Down Expand Up @@ -1387,8 +1430,15 @@ def headings(node):
badges += (f'<span class="section-badge section-status-{tone}">'
+ _html.escape(str(info.get('label') or status)) + '</span>')
title = page[heading['start']:heading['end']]
sources = [n for n in tree.nodes if n['tag'] == 'a' and n['end']
and 'board-source-link' in classes(n)
and heading['inner'] <= n['start'] < heading['close']]
source_links = ''.join(page[n['start']:n['end']] for n in sources)
for node in reversed(sources):
left, right = node['start'] - heading['start'], node['end'] - heading['start']
title = title[:left] + title[right:]
content = page[start:heading['start']] + page[heading['end']:end]
replacement = ('<details class="board-section"><summary>' + title + badges
replacement = ('<details class="board-section"><summary>' + title + badges + source_links
+ '</summary><div class="board-section-body">' + content + '</div></details>')
edits.append((start, end, replacement))
covered_until = end
Expand Down
10 changes: 10 additions & 0 deletions docs/board-navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,13 @@ synthetic test data, not current operational state.
![Brain desktop navigation](board-navigation/brain-desktop.png)

![Cortex phone navigation](board-navigation/cortex-phone.png)

### Markdown source links

The shared section layout renders links labelled “markdown version” as small
document icons, preserving the owner’s URL. Section icons sit at the right of
the summary after its count/status badges; page-level source icons sit at the
right of their utility row. Each link has a “Markdown version” tooltip and
accessible name, visible keyboard focus and a 44px click target. Following the
link does not toggle the disclosure. Other Markdown links retain their labels.
This presentation is applied when a consuming board is next regenerated.
2 changes: 1 addition & 1 deletion tests/test_board.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,7 @@ def test_html_is_self_contained_with_one_tap_payloads(tmp_path):
assert 'data-cmd="Use the intake skill."' in page
# The header line every sibling board carries: the markdown twin and the
# way back to the repository front door on github.com.
assert '<a href="board.md">markdown version</a>' in page
assert '<a href="board.md" class="board-source-link" title="Markdown version" aria-label="Markdown version"><svg' in page
assert ('<a href="https://github.com/ExampleOrg/PyAutoBrain/blob/main/'
'README.md">GitHub Page</a>') in page

Expand Down
4 changes: 2 additions & 2 deletions tests/test_intake_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -623,8 +623,8 @@ def test_html_sections_link_their_markdown_source(tmp_path):
"repos.yaml": REPOS_YAML})
html = _html(mind)
for src in ("active.md", "epics.md", "planned.md"):
assert f'/blob/main/{src}">markdown version</a>' in html, src
assert '/tree/main/draft">markdown version</a>' in html
assert f'/blob/main/{src}" title="Markdown version" aria-label="Markdown version"><svg' in html, src
assert '/tree/main/draft" title="Markdown version" aria-label="Markdown version"><svg' in html
# The page header also links back to the repository front door.
assert '/blob/main/README.md">GitHub Page</a>' in html

Expand Down
Loading