1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
# File drop and the entry line: design
Date: 2026-10-16. Status: approved in brainstorming, not implemented.
## Goal
SUPER+Home opens a single line, "How can I help you?", which grows into the
chat once a question is asked. A file dragged onto the island opens a middle
state offering what can be done with it, and asking about it is one of
those choices, not the only one. This follows Modesty's island drop (read
for ideas only; Modesty is GPLv3).
The daemon side (files in `ask`, extraction, history) is desktop-assistant's
`docs/superpowers/specs/2026-10-16-attachments-design.md`.
This is piece 1 of three. Piece 2 fills the file state's **Ask** row with
per-type model prompts, piece 3 its **Do** row with local file operations
(PDF to markdown, merge, video to audio, format or GIF, trackcrop). Each
gets its own spec.
## Three states
| state | when | the card shows |
| --- | --- | --- |
| entry | no turns, nothing staged | one line: "How can I help you?" |
| file | files staged, no turns | the file chips, the action rows, the input ("Ask about report.pdf…"), **Chat about it** |
| chat | the conversation has turns | the chat as today |
The card stays centred, with the dim backdrop in every state. Its height
animates (about 180 ms) between states, growing from the centre.
- **Opening.** SUPER+Home shows the chat if the current conversation was
active (a `user` or `done` event) in the last 10 minutes. Otherwise the
canvas sends `new`, and the empty conversation is the entry state. The
old conversation stays in History. The 10 minutes are a constant, not a
preference.
- **Entry to chat.** Enter on a typed question sends `ask`; its `user`
event adds a turn, which is the chat state.
- **A drop** always starts a new task: the canvas sends `new`, stages the
files and shows the file state, even over a recent conversation.
- **File state.** Each chip has a ×; removing the last returns to entry.
Enter on a typed question sends `ask {text, files}` and clears the chips.
**Chat about it** moves to the chat layout with the files still staged
and focus in the input, sending nothing. In piece 1 the action rows hold
only Chat about it. A file of a kind chat does not take (video, archive)
shows "chat supports text, images and PDF" on its chip; while any such
chip is staged, Chat about it and the input are disabled (removing it
enables them).
- **Results of a local operation** (piece 3) report in the file state and
never turn it into a chat.
- Escape closes the card in every state, after the history panel.
Sent files show as chips under the question in the transcript, with the
`truncated` mark from the daemon's `files` list.
## Attaching from the chat
An attach button, the paperclip `U+F0C6` (nf-fa-paperclip, present in
Inconsolata Nerd Font, checked against its cmap), sits left of the input
in the file and chat states. It opens a picker drawn inside the card. A
system file dialog cannot be used: the card is an overlay layer surface
holding the keyboard, so a dialog window, Qt's or the portal's, would open
beneath it out of reach. Modesty draws its own picker for the same reason.
- **The panel** slides over the transcript like History: a path bar with
Home and an up button, a filter field narrowing the current directory as
you type, and a list from `Qt.labs.folderlistmodel` (installed), folders
first.
- **Choosing.** A click on a folder enters it; a click on a file stages it
as a chip and closes the panel; Ctrl+click stages and keeps it open, for
several files. Up and Down move the highlight, Enter opens it.
- It opens in the directory last picked from, `~` the first time. Hidden
files are listed only when the filter starts with `.`.
- **Escape** closes the picker first, then History, then the card.
- **In the chat state** the staged files sit as chips above the input and
the next `ask` carries them in the same conversation; there is no file
state there. A chip of a kind chat does not take disables Send until it
is removed, as in the file state.
## File kinds
`desktop/assistant/Files.js` maps a path to `text`, `image`, `pdf`,
`video`, `archive` or `other` by extension, with the same text, code and
image lists as the daemon. It also turns a dropped URL into a path:
`file://` URLs are decoded, anything else is ignored.
## The island receptacle
In `desktop/island/Island.qml`:
- one `DropArea` spans the sliver and the space for a bubble below it;
- a drag entering with URLs and a copy action sets `dropHold`, which shows
a bubble, "Drop for the assistant", and adds the bubble to the input
mask so the drop can land on it;
- leaving clears `dropHold`;
- a drop converts the URLs, calls `asst.attach(paths)` and accepts the
copy. With no file URLs it is refused.
`shell.qml` passes the canvas as `asst: assistant`, never under the
canvas's own id name (AGENTS.md: a property named like the enclosing id
binds to itself).
Wayland reports a drag only once it enters one of the island's input
regions, which is why the drop begins on the sliver.
## Testing
- `node desktop/assistant/test_files.js`: kinds per extension, case
folding, files with no extension, URL decoding with spaces and non-ASCII
names, non-file URLs dropped.
- The scratch harness load check, as for the canvas.
- By hand: drag a file over the sliver and see the bubble; drop it and get
the file state; remove the chip and get entry; drop again and use Chat
about it; ask about a PDF and an image; see chips in the transcript;
drop a video and see the refusal; SUPER+Home after more than 10 minutes
gives entry, within 10 minutes the chat; in a chat, the paperclip picker
stages one file by click and two by Ctrl+click, and Escape closes the
picker before the card.
|