Skip to content

Commit cee9c76

Browse files
fix(skills): read-only view mode for built-in skills (#494)
* fix(skills): read-only view mode for built-in skills - SkillCard shows Eye icon + "View" for built-in, Pencil + "Edit" for user - SkillDialog in read-only mode: disabled fields, no toolbar on markdown editor, "View Skill" title, "Close" button, no "Update Skill" - Hide tip section in read-only mode * fix(skills): use react-markdown for read-only skill view Replace MDXEditor with react-markdown for viewing built-in skills. MDXEditor chokes on code fences, angle brackets, and image syntax causing content truncation. react-markdown handles standard markdown correctly with no rendering issues.
1 parent 7bdeeb8 commit cee9c76

3 files changed

Lines changed: 73 additions & 39 deletions

File tree

packages/browseros-agent/apps/agent/entrypoints/app/skills/SkillsPage.tsx

Lines changed: 66 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AlertCircle, Pencil, Plus, Trash2, Wand2 } from 'lucide-react'
1+
import { AlertCircle, Eye, Pencil, Plus, Trash2, Wand2 } from 'lucide-react'
22
import { type FC, useEffect, useState } from 'react'
33
import { toast } from 'sonner'
44
import {
@@ -26,6 +26,7 @@ import { Label } from '@/components/ui/label'
2626
import { MarkdownEditor } from '@/components/ui/MarkdownEditor'
2727
import { Switch } from '@/components/ui/switch'
2828
import { Textarea } from '@/components/ui/textarea'
29+
import Markdown from 'react-markdown'
2930
import { type SkillDetail, type SkillMeta, useSkills } from './useSkills'
3031

3132
const loadingSkillCards = [
@@ -120,6 +121,7 @@ export const SkillsPage: FC = () => {
120121
open={isDialogOpen}
121122
onOpenChange={setIsDialogOpen}
122123
editingSkill={editingSkill}
124+
readOnly={editingSkill?.builtIn}
123125
onSave={async (data) => {
124126
try {
125127
if (editingSkill) {
@@ -327,8 +329,11 @@ const SkillCard: FC<{
327329
onClick={onEdit}
328330
className="-ml-2 h-7 px-2 text-muted-foreground hover:bg-transparent hover:text-foreground"
329331
>
330-
<Pencil className="size-3.5" />
331-
Edit
332+
{skill.builtIn ? (
333+
<><Eye className="size-3.5" />View</>
334+
) : (
335+
<><Pencil className="size-3.5" />Edit</>
336+
)}
332337
</Button>
333338
{!skill.builtIn ? (
334339
<Button
@@ -350,12 +355,13 @@ const SkillDialog: FC<{
350355
open: boolean
351356
onOpenChange: (open: boolean) => void
352357
editingSkill: SkillDetail | null
358+
readOnly?: boolean
353359
onSave: (data: {
354360
name: string
355361
description: string
356362
content: string
357363
}) => Promise<void>
358-
}> = ({ open, onOpenChange, editingSkill, onSave }) => {
364+
}> = ({ open, onOpenChange, editingSkill, readOnly, onSave }) => {
359365
const [name, setName] = useState('')
360366
const [description, setDescription] = useState('')
361367
const [content, setContent] = useState('')
@@ -402,12 +408,14 @@ const SkillDialog: FC<{
402408
<DialogContent className="flex max-h-[90vh] flex-col gap-0 overflow-hidden p-0 sm:max-w-5xl">
403409
<DialogHeader className="border-b px-6 py-5">
404410
<DialogTitle>
405-
{editingSkill ? 'Edit Skill' : 'Create Skill'}
411+
{readOnly ? 'View Skill' : editingSkill ? 'Edit Skill' : 'Create Skill'}
406412
</DialogTitle>
407413
<DialogDescription>
408-
{editingSkill
409-
? 'Refine when the agent should use this skill and how it should execute it.'
410-
: 'Define a reusable instruction set your agent can apply when a request matches.'}
414+
{readOnly
415+
? 'This skill is managed by BrowserOS and updated automatically.'
416+
: editingSkill
417+
? 'Refine when the agent should use this skill and how it should execute it.'
418+
: 'Define a reusable instruction set your agent can apply when a request matches.'}
411419
</DialogDescription>
412420
</DialogHeader>
413421

@@ -421,6 +429,7 @@ const SkillDialog: FC<{
421429
value={name}
422430
onChange={(event) => setName(event.target.value)}
423431
maxLength={100}
432+
readOnly={readOnly}
424433
/>
425434
<p className="text-muted-foreground text-xs leading-5">
426435
Keep it short and recognizable in the skills list.
@@ -436,19 +445,22 @@ const SkillDialog: FC<{
436445
onChange={(event) => setDescription(event.target.value)}
437446
maxLength={500}
438447
className="min-h-28 resize-none bg-background"
448+
readOnly={readOnly}
439449
/>
440450
<p className="text-muted-foreground text-xs leading-5">
441451
This is the trigger summary the agent uses to pick the skill.
442452
</p>
443453
</div>
444454

445-
<div className="mt-auto rounded-lg border border-border/60 border-dashed bg-muted/30 px-3 py-2.5">
446-
<p className="font-medium text-muted-foreground text-xs">Tip</p>
447-
<ul className="mt-1.5 list-disc space-y-1 pl-4 text-muted-foreground text-xs leading-5">
448-
<li>List the ordered steps the agent should follow.</li>
449-
<li>Close with the output or formatting you expect back.</li>
450-
</ul>
451-
</div>
455+
{!readOnly ? (
456+
<div className="mt-auto rounded-lg border border-border/60 border-dashed bg-muted/30 px-3 py-2.5">
457+
<p className="font-medium text-muted-foreground text-xs">Tip</p>
458+
<ul className="mt-1.5 list-disc space-y-1 pl-4 text-muted-foreground text-xs leading-5">
459+
<li>List the ordered steps the agent should follow.</li>
460+
<li>Close with the output or formatting you expect back.</li>
461+
</ul>
462+
</div>
463+
) : null}
452464
</div>
453465

454466
<div className="flex min-h-0 flex-col px-6 py-5">
@@ -459,36 +471,52 @@ const SkillDialog: FC<{
459471
</Badge>
460472
</div>
461473

462-
<MarkdownEditor
463-
id="skill-content"
464-
value={content}
465-
onChange={setContent}
466-
onKeyDown={handleContentKeyDown}
467-
placeholder="Write instructions for the agent. Use markdown for structure."
468-
className="mt-4 min-h-[320px] flex-1 overflow-y-auto text-sm"
469-
/>
474+
{readOnly ? (
475+
<div className="prose prose-sm mt-4 min-h-[320px] max-w-none flex-1 overflow-y-auto rounded-md border p-4 text-sm dark:prose-invert">
476+
<Markdown>{content}</Markdown>
477+
</div>
478+
) : (
479+
<MarkdownEditor
480+
id="skill-content"
481+
value={content}
482+
onChange={setContent}
483+
onKeyDown={handleContentKeyDown}
484+
placeholder="Write instructions for the agent. Use markdown for structure."
485+
className="mt-4 min-h-[320px] flex-1 overflow-y-auto text-sm"
486+
/>
487+
)}
470488
</div>
471489
</div>
472490

473491
<div className="flex flex-col gap-3 border-t px-6 py-4 sm:flex-row sm:items-center sm:justify-between">
474492
<p className="text-muted-foreground text-xs">
475-
Saved locally and available to your agent immediately.
493+
{readOnly
494+
? 'This skill is managed by BrowserOS and updated automatically.'
495+
: 'Saved locally and available to your agent immediately.'}
476496
</p>
477497
<div className="flex flex-col-reverse gap-2 sm:flex-row">
478-
<Button
479-
variant="outline"
480-
onClick={() => onOpenChange(false)}
481-
disabled={saving}
482-
>
483-
Cancel
484-
</Button>
485-
<Button onClick={handleSubmit} disabled={!isValid || saving}>
486-
{saving
487-
? 'Saving...'
488-
: editingSkill
489-
? 'Update Skill'
490-
: 'Create Skill'}
491-
</Button>
498+
{readOnly ? (
499+
<Button variant="outline" onClick={() => onOpenChange(false)}>
500+
Close
501+
</Button>
502+
) : (
503+
<>
504+
<Button
505+
variant="outline"
506+
onClick={() => onOpenChange(false)}
507+
disabled={saving}
508+
>
509+
Cancel
510+
</Button>
511+
<Button onClick={handleSubmit} disabled={!isValid || saving}>
512+
{saving
513+
? 'Saving...'
514+
: editingSkill
515+
? 'Update Skill'
516+
: 'Create Skill'}
517+
</Button>
518+
</>
519+
)}
492520
</div>
493521
</div>
494522
</DialogContent>

packages/browseros-agent/apps/agent/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"react": "^19.1.1",
8080
"react-dom": "^19.1.1",
8181
"react-hook-form": "^7.66.1",
82+
"react-markdown": "^10.1.0",
8283
"react-resizable-panels": "^4.3.3",
8384
"react-router": "^7.12.0",
8485
"shiki": "^3.15.0",

packages/browseros-agent/bun.lock

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)