Workspace
The files an agent has put aside while it works, so you can see what it actually produced. Give it paths; it folds them into a tree_view of folders, with a glyph for each kind of file.
Files, beside a plan
Two sections of one panel, with a divider between them. A file with a url is a link, and its glyph comes from its extension unless icon: says otherwise.
Plan
1/2
- DoneGather references
- In progressDraft the scene
Workspace
4
references
- scene.json
- notes.md
<div class="w-80 rounded-xl border border-neutral-200 bg-white dark:border-neutral-800 dark:bg-neutral-900">
<%= ai_chat_plan id: "workspace_plan", open: false do |plan| %>
<% plan.step "Gather references", state: :completed %>
<% plan.step "Draft the scene", state: :in_progress %>
<% end %>
<%= ai_chat_workspace id: "workspace_files" do |workspace| %>
<% workspace.file "references/lantern-red.png", size: 1_480_000, url: "#" %>
<% workspace.file "references/market-night.jpg", size: 2_210_000, url: "#" %>
<% workspace.file "scene.json", size: 6_120 %>
<% workspace.file "notes.md", size: 812 %>
<% end %>
</div>Nested folders and a long path
Folders open and fold, and come before the files beside them. A folder that holds only a folder joins it in one row, which shortens from the front so the last folder's name stays; a long file name truncates only when it alone doesn't fit. Each row's title is its full path. The count is the number of files.
Workspace
18
captures
careers.example-international.com/jobs
data
raw
- applicants.json
- resumes.zip
- applicants.csv
renders
- shortlist.png
- summary.md
<div class="w-72 rounded-xl border border-neutral-200 bg-white dark:border-neutral-800 dark:bg-neutral-900">
<%= ai_chat_workspace id: "workspace_folders" do |workspace| %>
<% workspace.file "summary.md", size: 2_400, url: "#" %>
<% workspace.file "captures/careers.example-international.com/jobs/senior-platform-engineer-infrastructure-and-reliability.html", size: 84_200, url: "#" %>
<% %w[acme bluebird cobalt driftwood ember fernway goldcrest harbour ironbark juniper kestrel lumen].each_with_index do |company, index| %>
<% workspace.file "captures/boards/#{company}.html", size: 18_000 + (index * 2_750), url: "#" %>
<% end %>
<% workspace.file "data/applicants.csv", size: 12_900 %>
<% workspace.file "data/raw/applicants.json", size: 48_300 %>
<% workspace.file "data/raw/resumes.zip", size: 3_400_000 %>
<% workspace.file "renders/shortlist.png", size: 412_000 %>
<% end %>
</div>In a host's warm greys
The same panel with Tailwind's neutral ramp redefined on its wrapper, so its text, borders and surfaces share one warm palette. Each shade keeps neutral's lightness, so contrast holds in light and dark. Nothing else on the page changes.
Plan
1/2
- DoneGather references
- In progressDraft the scene
Workspace
3
references
- scene.json
- notes.md
<%# A host's warm greys, scoped to this panel: the neutral ramp redefined on the
wrapper, at the same lightness as Tailwind's so every text pair keeps its
contrast. In an app this rule lives in your Tailwind input file. %>
<% warm_greys = {
50 => "98.5% 0.004", 100 => "97% 0.007", 200 => "92.2% 0.012", 300 => "87% 0.016",
400 => "70.8% 0.024", 500 => "55.6% 0.03", 600 => "43.9% 0.028", 700 => "37.1% 0.025",
800 => "26.9% 0.02", 900 => "20.5% 0.016", 950 => "14.5% 0.012"
}.map { |shade, value| "--color-neutral-#{shade}: oklch(#{value} 70);" }.join(" ") %>
<div style="<%= warm_greys %>" class="w-80 rounded-xl border border-neutral-200 bg-neutral-50 dark:border-neutral-800 dark:bg-neutral-900">
<%= ai_chat_plan id: "warm_greys_plan" do |plan| %>
<% plan.step "Gather references", state: :completed %>
<% plan.step "Draft the scene", state: :in_progress %>
<% end %>
<%= ai_chat_workspace id: "warm_greys_files" do |workspace| %>
<% workspace.file "references/lantern-red.png", size: 1_480_000, url: "#" %>
<% workspace.file "scene.json", size: 6_120 %>
<% workspace.file "notes.md", size: 812, icon: :pencil %>
<% end %>
</div>