Skip to content
Open
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
5 changes: 4 additions & 1 deletion .jules/palette.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 2024-06-01 - Dynamic Tooltips for Disabled States
**Learning:** Found that static tooltips like "Send message" on disabled submit buttons can be confusing, particularly when users don't know *why* the button is disabled. In React/Next.js, toggling the `title` attribute dynamically based on the same condition that disables the button (`!input.trim() ? "Enter a message to send" : "Send message"`) provides immediate contextual feedback for screen reader and mouse users alike.
**Action:** When creating form submit buttons, always tie the `title` or `aria-label` attribute dynamically to the validation state, and ensure visual disabled styling (`disabled:cursor-not-allowed`) is coupled with `focus-visible` styles for comprehensive accessibility.
**Action:** When creating form submit buttons, always tie the `title` or `aria-label` attribute dynamically to the validation state, and ensure visual disabled styling (`disabled:cursor-not-allowed`) is coupled with `focus-visible` styles for comprehensive accessibility.
## 2026-06-26 - Network-State Driven UI Feedback
**Learning:** When core interactive elements (like chat inputs) depend on a network or tunnel state (e.g., `tunnelStatus`), simply disabling the element can leave users confused if the placeholder text still implies they can interact with it (e.g. "Inject knowledge..."). Dynamically updating both the placeholder to indicate the offline state (e.g. "Neuro-Link Offline...") and the disabled `aria-label`/`title` provides necessary context.
**Action:** Always link contextual UI properties (placeholders, tooltips, aria-labels) to the same underlying connection/network state that drives the disabled state.
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const ThoughtItem = memo(function ThoughtItem({ thought }: { thought: string })
)});

export default function ChatInterface() {
const { messages, sendMessage, isLearning, pulseType, thoughts } = useApeiron();
const { messages, sendMessage, isLearning, pulseType, thoughts, tunnelStatus } = useApeiron();
const [input, setInput] = useState('');
const scrollRef = useRef<HTMLDivElement>(null);

Expand Down Expand Up @@ -135,15 +135,16 @@ export default function ChatInterface() {
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Inject knowledge into the kernel..."
placeholder={tunnelStatus === 'LOCKED' ? "Inject knowledge into the kernel..." : "Neuro-Link Offline..."}
aria-label="Message input"
className="flex-1 bg-gray-900 border border-gray-700 rounded-lg px-4 py-3 focus:outline-none focus:border-green-500 transition-colors text-white"
disabled={tunnelStatus !== 'LOCKED'}
className="flex-1 bg-gray-900 border border-gray-700 rounded-lg px-4 py-3 focus:outline-none focus:border-green-500 transition-colors text-white disabled:opacity-50 disabled:cursor-not-allowed"
/>
<button
type="submit"
disabled={!input.trim()}
aria-label={!input.trim() ? "Cannot send empty message" : "Send message"}
title={!input.trim() ? "Enter a message to send" : "Send message"}
disabled={tunnelStatus !== 'LOCKED' || !input.trim()}
aria-label={tunnelStatus !== 'LOCKED' ? "System offline" : (!input.trim() ? "Cannot send empty message" : "Send message")}
title={tunnelStatus !== 'LOCKED' ? "System offline" : (!input.trim() ? "Enter a message to send" : "Send message")}
className="p-3 bg-gray-800 hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed rounded-lg text-green-500 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-green-500 focus-visible:ring-offset-2 focus-visible:ring-offset-black"
>
<Send size={20} />
Expand Down
Loading