fix(workspace): hidden bullet actions on phones, no second extract, notice layout
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m20s

- The floating bullet actions only react to hover on devices with a real
  pointer; a tap on a phone made the invisible buttons clickable.
- A bullet that is only a note link (plus hashtags) can no longer be
  extracted: button and /extract are hidden, the server answers 400.
- Extract notice on phones sits above the bottom navigation, text on
  top (two lines max) with the actions below.
- Release v0.26.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-10-31 19:56:32 +01:00
1 parent fdd6e38065
commit 68bc4d279c
12 files changed
+78 -27

No files matched your search

@@ -1372,23 +1372,28 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
{notice && (
<div
role="status"
className="fixed bottom-6 left-1/2 -translate-x-1/2 z-50 max-w-[90vw] px-3 py-2 rounded-card text-xs text-main bg-panel-elevated border border-border shadow-panel"
className="fixed bottom-[calc(5.5rem+env(safe-area-inset-bottom))] sm:bottom-6 left-1/2 -translate-x-1/2 z-50 w-[calc(100vw-1.5rem)] sm:w-auto sm:max-w-[90vw] flex flex-col sm:block pl-4 pr-1 pt-3 pb-1 sm:px-3 sm:py-2 rounded-card text-sm sm:text-xs text-main bg-panel-elevated border border-border shadow-panel"
>
<span>{notice}</span>
{noticeActions.map((a) => (
<button
key={a.label}
type="button"
onClick={() => {
setNotice(null);
setNoticeActions([]);
a.run();
}}
className="ml-3 font-semibold text-accent hover:underline min-h-[44px] sm:min-h-0"
>
{a.label}
</button>
))}
{/* Phones: text on top (two lines at most), buttons below; desktop: one line */}
<span className="max-sm:pr-3 max-sm:line-clamp-2">{notice}</span>
{noticeActions.length > 0 && (
<span className="flex justify-end sm:inline">
{noticeActions.map((a) => (
<button
key={a.label}
type="button"
onClick={() => {
setNotice(null);
setNoticeActions([]);
a.run();
}}
className="h-11 px-3 sm:h-auto sm:px-0 sm:ml-3 font-semibold text-accent hover:underline"
>
{a.label}
</button>
))}
</span>
)}
</div>
)}
</div>
@@ -42,6 +42,7 @@ import { mediaContent, parseMediaContent } from '../../lib/media';
import { renderInlineMarkdown } from '../../lib/markdown';
import { api } from '../../lib/api';
import { safeHref } from '../../lib/safeUrl';
import { isNoteLinkOnly } from '../../lib/wikiLink';
export interface SlashCommand {
type: BlockType | 'link' | 'dictation' | 'extract';
@@ -459,7 +460,7 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
// /extract only when note has content and is at start/end
if (c.type === 'extract') {
return !isRestEmpty && isAtStartOrEnd && !!onExtractBlock;
return !isRestEmpty && isAtStartOrEnd && !!onExtractBlock && !isNoteLinkOnly(restOfBlock);
}
// /h1, /h2, /h3, /todo, /callout, /text (type 'bullet') at start or at end
@@ -1579,7 +1580,8 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
className={`absolute right-1 sm:right-2 top-0.5 sm:top-1 z-20 bg-panel/90 backdrop-blur-sm border border-border shadow-xs rounded-pill px-1 sm:px-1.5 py-0.5 flex items-center gap-0.5 sm:gap-1 transition-opacity ${
activeFocus
? 'opacity-100 pointer-events-auto'
: 'opacity-0 group-hover:opacity-100 focus-within:opacity-100 pointer-events-none group-hover:pointer-events-auto focus-within:pointer-events-auto'
: // Hover only with a real pointer: a tap on a phone would make the invisible buttons clickable
'opacity-0 [@media(hover:hover)]:group-hover:opacity-100 focus-within:opacity-100 pointer-events-none [@media(hover:hover)]:group-hover:pointer-events-auto focus-within:pointer-events-auto'
}`}
>
{/* Convert back to bullet for headings, callouts, dividers, code and numbered items */}
@@ -1702,7 +1704,7 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
)}
{/* Branch into its own note */}
{canEdit && onExtractBlock && localContent.trim() !== '' && !mediaPicker && (
{canEdit && onExtractBlock && localContent.trim() !== '' && !isNoteLinkOnly(localContent) && !mediaPicker && (
<button
type="button"
onClick={() => onExtractBlock(block.id)}