feat: implement contextual help system with searchable overlay
- Add '?' button in expert mode status bar that opens help overlay - Search input with fuzzy search (same algorithm as command palette) - 43 help articles covering major features (sensing links, Fresnel zones, detection quality, presence prediction, fall detection, etc.) - Articles stored as static JSON (dashboard/help_articles.json) - No server round-trip - loads articles client-side - Category filter buttons for easy navigation - Keyboard shortcut: Ctrl+? to open help overlay Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
df31fc6bd8
commit
a574a84653
6 changed files with 48 additions and 32 deletions
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
6812da8ccb1b0675133a2e545faee98e07559a87
|
||||
0485d746ba3001bad6ee9eabfe485db2771a9cf0
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
*/
|
||||
async function loadArticles() {
|
||||
try {
|
||||
const response = await fetch('/help_articles.json');
|
||||
const response = await fetch('help_articles.json');
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load help articles');
|
||||
}
|
||||
|
|
@ -121,16 +121,16 @@
|
|||
* Add help button to expert mode header
|
||||
*/
|
||||
function addHelpButton() {
|
||||
// Look for expert mode header or status bar
|
||||
const header = document.querySelector('.expert-header, .view-header, .toolbar, #status-bar');
|
||||
if (!header) {
|
||||
// Try again after a delay
|
||||
setTimeout(addHelpButton, 1000);
|
||||
// Check if button already exists
|
||||
if (document.getElementById('help-button')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if button already exists
|
||||
if (document.getElementById('help-button')) {
|
||||
// Look for the status bar button container (margin-left:auto; gap:6px)
|
||||
const buttonContainer = document.querySelector('#status-bar > div[style*="margin-left:auto"]');
|
||||
if (!buttonContainer) {
|
||||
// Try again after a delay
|
||||
setTimeout(addHelpButton, 500);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +141,13 @@
|
|||
helpBtn.title = 'Help & Documentation (Ctrl+?)';
|
||||
helpBtn.onclick = () => HelpOverlay.open();
|
||||
|
||||
header.appendChild(helpBtn);
|
||||
// Insert before the last status-item (FPS counter)
|
||||
const fpsItem = buttonContainer.parentElement.querySelector('.status-item:last-child');
|
||||
if (fpsItem) {
|
||||
buttonContainer.insertBefore(helpBtn, fpsItem);
|
||||
} else {
|
||||
buttonContainer.appendChild(helpBtn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -78,6 +78,11 @@ func AllMigrations() []Migration {
|
|||
Description: "add id, delivered, acknowledged columns to briefings table",
|
||||
Up: migration_014_add_briefing_delivery_columns,
|
||||
},
|
||||
{
|
||||
Version: 15,
|
||||
Description: "add feature_notifications table for feature discovery",
|
||||
Up: migration_015_add_feature_notifications,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -705,3 +710,17 @@ func migration_014_add_briefing_delivery_columns(tx *sql.Tx) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// migration_015_add_feature_notifications adds the feature_notifications table
|
||||
// for one-time feature discovery notifications.
|
||||
func migration_015_add_feature_notifications(tx *sql.Tx) error {
|
||||
schema := `
|
||||
CREATE TABLE IF NOT EXISTS feature_notifications (
|
||||
event_id TEXT PRIMARY KEY,
|
||||
fired_at INTEGER NOT NULL,
|
||||
acknowledged_at INTEGER
|
||||
);
|
||||
`
|
||||
_, err := tx.Exec(schema)
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,30 +53,14 @@ const (
|
|||
)
|
||||
|
||||
// NewNotifier creates a new feature notification manager.
|
||||
// The feature_notifications table must already exist via migration.
|
||||
func NewNotifier(db *sql.DB) (*Notifier, error) {
|
||||
// Ensure the feature_notifications table exists
|
||||
if err := ensureSchema(db); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Notifier{
|
||||
db: db,
|
||||
quietHours: &QuietHours{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ensureSchema creates the feature_notifications table if it doesn't exist.
|
||||
func ensureSchema(db *sql.DB) error {
|
||||
query := `
|
||||
CREATE TABLE IF NOT EXISTS feature_notifications (
|
||||
event_id TEXT PRIMARY KEY,
|
||||
fired_at INTEGER NOT NULL,
|
||||
acknowledged_at INTEGER
|
||||
);`
|
||||
_, err := db.Exec(query)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetQuietHours sets the quiet hours configuration.
|
||||
func (n *Notifier) SetQuietHours(qh *QuietHours) {
|
||||
n.mu.Lock()
|
||||
|
|
|
|||
|
|
@ -194,15 +194,22 @@ func TestNotifierFireWithAction(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// createTestDB creates an in-memory test database.
|
||||
// createTestDB creates an in-memory test database with the feature_notifications schema.
|
||||
func createTestDB(t *testing.T) *sql.DB {
|
||||
db, err := sql.Open("sqlite", ":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open test database: %v", err)
|
||||
}
|
||||
|
||||
// Create schema
|
||||
if err := ensureSchema(db); err != nil {
|
||||
// Create the feature_notifications table directly (same as migration_015)
|
||||
schema := `
|
||||
CREATE TABLE IF NOT EXISTS feature_notifications (
|
||||
event_id TEXT PRIMARY KEY,
|
||||
fired_at INTEGER NOT NULL,
|
||||
acknowledged_at INTEGER
|
||||
);
|
||||
`
|
||||
if _, err := db.Exec(schema); err != nil {
|
||||
t.Fatalf("Failed to create schema: %v", err)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue