docs(skills): state what mobile actually does with a project mention

The projects skill told agents a `mention://project/<uuid>` link "renders as a
navigable project chip on web, desktop, and mobile", and that a pasted project
URL is unfurled into that same chip by "the reader's client". Neither holds on
mobile: `apps/mobile/lib/markdown/markdown.tsx` renders the default enriched
link and only routes the tap, and a bare URL still goes to `Linking.openURL`,
which leaves the app.

These files enter agent context and read as product contract, so an agent
choosing between a mention link and a pasted URL was choosing on false
information — and the URL is the option that strands a mobile reader in a
browser. Both skills and both source maps now say chip on web/desktop, ordinary
link that opens the project on tap on mobile, and unfurling as web/desktop only.

The projects skill also now states the preference outright rather than
presenting the two forms as equivalent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Naiyuan Qing
2026-07-31 16:00:18 +08:00
parent ff5169084d
commit 7bf76be012
4 changed files with 14 additions and 9 deletions

View File

@@ -35,7 +35,8 @@ goes.
One `mention://` form deliberately sits OUTSIDE this parser:
`[Label](mention://project/<uuid>)`. `project` is absent from the type group
above, so the backend never parses it and it can enqueue nothing — it is a
render-only link the clients turn into a project chip. That is the whole point:
render-only link every client makes navigable (a chip on web and desktop, an
ordinary link that opens the project on tap on mobile). That is the whole point:
a project reference should never be able to start a run. Use it freely to point
at a project (see the multica-projects-and-resources skill); everything else in
this document is about the four types (plus `all`) the parser does recognize.

View File

@@ -15,7 +15,7 @@ a pointer.
| `ParseMentions` extracts and dedups `{Type, ID}` from `m[2]`/`m[3]` | `server/internal/util/mention.go:24-37` |
| `Mention.Type` doc enum = "member", "agent", "issue", or "all" (squad added in regex) | `server/internal/util/mention.go:7` |
| `HasMentionAll` reports whether any parsed mention is `all` | `server/internal/util/mention.go:40-47` |
| **`project` is NOT in the type group** — `[Label](mention://project/<uuid>)` never parses, so it can enqueue nothing. It is a render-only link the clients turn into a project chip (`RichLink` in `packages/views/rich-content/rich-content.tsx`; tap handling in `apps/mobile/lib/markdown/markdown.tsx`) | `server/internal/util/mention.go:16` |
| **`project` is NOT in the type group** — `[Label](mention://project/<uuid>)` never parses, so it can enqueue nothing. It is a render-only link every client makes navigable — a chip on web/desktop (`RichLink` in `packages/views/rich-content/rich-content.tsx`), an ordinary enriched link whose tap is routed on mobile (`onLinkPress` in `apps/mobile/lib/markdown/markdown.tsx`, which renders no chip) | `server/internal/util/mention.go:16` |
### Parser behavior tests (pin the example shapes the skill uses)

View File

@@ -64,12 +64,16 @@ the mention-link form instead, with the project UUID from
[Roadmap](mention://project/<project-id>)
It renders as a navigable project chip on web, desktop, and mobile. Unlike
`@agent` / `@squad`, it is a pure link: `util.MentionRe` does not even include
`project`, so it enqueues nothing and notifies nobody — the same
no-side-effect contract as an `issue` mention. Pasting the project's URL
(`<app>/<slug>/projects/<uuid>`) works too; the reader's client unfurls a bare
in-app entity URL into that same chip.
Every client makes it navigable, with different presentation: web and desktop
render a chip carrying the project's icon and current title, while mobile
renders an ordinary link that opens the project on tap. Unlike `@agent` /
`@squad`, it is a pure link: `util.MentionRe` does not even include `project`,
so it enqueues nothing and notifies nobody — the same no-side-effect contract
as an `issue` mention.
Prefer this form over pasting the project's URL. Web and desktop do unfurl a
bare in-app project URL into that same chip, but mobile does not — there a
pasted URL is handed to the system browser and takes the reader out of the app.
## When to add a resource

View File

@@ -10,5 +10,5 @@
- `server/pkg/db/queries/project_resource.sql` is the CRUD query surface for `project_resource` rows.
- Project resources are written into `.multica/project/resources.json` for agent workdirs.
- `github_repo.resource_ref.ref` is lifted into daemon `RepoData.Ref` by `server/internal/handler/daemon.go`; `server/internal/daemon/daemon.go` stores it per task, and `server/internal/daemon/health.go` uses it as the default `/repo/checkout` ref when the checkout request does not explicitly pass one.
- Referring to a project in a comment: `[Label](mention://project/<uuid>)` is a render-only link. `util.MentionRe` (`server/internal/util/mention.go`) does NOT include `project` in its type group, so the backend never parses it and it can enqueue nothing — deliberately weaker than `agent` / `squad`. The frontend renders it as a chip in `RichLink` (`packages/views/rich-content/rich-content.tsx`) and the editor's `MentionView` (`packages/views/editor/extensions/mention-view.tsx`); mobile navigates on tap in `apps/mobile/lib/markdown/markdown.tsx`. A bare in-app project/issue URL is unfurled into the same chip by `parseWorkspaceEntityLink` (`packages/views/editor/utils/link-handler.ts`), which requires no query/fragment, the current workspace slug, and an id that addresses one entity — a UUID, or an issue identifier (`MUL-123`), which is the form `copyLink` produces and which falls back to the plain link when it resolves to nothing (MUL-5499). The agent-facing line lives in the runtime brief's Mentions section (`writeMentions`, `server/internal/daemon/execenv/runtime_config_sections.go`).
- Referring to a project in a comment: `[Label](mention://project/<uuid>)` is a render-only link. `util.MentionRe` (`server/internal/util/mention.go`) does NOT include `project` in its type group, so the backend never parses it and it can enqueue nothing — deliberately weaker than `agent` / `squad`. Presentation differs by client and the skill says so: web and desktop render a chip in `RichLink` (`packages/views/rich-content/rich-content.tsx`) and the editor's `MentionView` (`packages/views/editor/extensions/mention-view.tsx`), while mobile has no chip — `apps/mobile/lib/markdown/markdown.tsx` leaves the default enriched-link styling and only routes the tap (`onLinkPress`). Unfurling a bare in-app project/issue URL is web/desktop only, via `parseWorkspaceEntityLink` (`packages/views/editor/utils/link-handler.ts`), which requires same origin (resolved through `URL`, not a prefix test), no query/fragment, the current workspace slug, and an id that addresses one entity — a UUID, or an issue identifier (`MUL-123`), which is the form `copyLink` produces and which falls back to the plain link when it resolves to nothing (MUL-5499). On mobile a bare URL still goes to `Linking.openURL`. The agent-facing line lives in the runtime brief's Mentions section (`writeMentions`, `server/internal/daemon/execenv/runtime_config_sections.go`).
- A project's `description` is injected as durable context for every task in the project. The claim handler (`server/internal/handler/daemon.go`) reads `proj.Description` onto the claim response (`ProjectDescription`, `server/internal/handler/agent.go`); the daemon carries it through `Task` (`server/internal/daemon/types.go`) and `TaskContextForEnv` (`server/internal/daemon/execenv/execenv.go`) into the brief's `## Project Context` section (`server/internal/daemon/execenv/runtime_config.go`) and into `.multica/project/resources.json` as `project_description` (`server/internal/daemon/execenv/context.go`).