Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/link-signals-taxonomy.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
---
"@plotday/twister": minor
---

Added: `LinkSignals.taxonomy` — namespaced structured-container keys
(`"<connector>:<kind>:<id>"`, e.g. `"linear:project:<uuid>"`) a connector
emits for the project, repository, board or folder an item lives in. The
platform matches these exactly against priority bindings to route items to
the user's priorities without a model call. Use immutable ids, never display
names; omit the field when the source has no structured container.
16 changes: 16 additions & 0 deletions connectors/linear/src/linear-sync.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -181,3 +181,19 @@ describe("buildIssueLink", () => {
});
});
});

describe("buildIssueLink taxonomy signals", () => {
it("emits the issue's project as a namespaced taxonomy key", () => {
const link = buildIssueLink(
issue({ project: { id: "proj-7" } }),
"team-9",
false
);
expect(link.signals?.taxonomy).toEqual(["linear:project:proj-7"]);
});

it("omits taxonomy when the issue has no project", () => {
const link = buildIssueLink(issue(), "team-9", false);
expect(link.signals?.taxonomy).toBeUndefined();
});
});
10 changes: 10 additions & 0 deletions connectors/linear/src/linear-sync.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,6 +57,7 @@ export const TEAM_ISSUES_BATCH_QUERY = `
creator { id name email avatarUrl }
assignee { id name email avatarUrl }
state { id type }
project { id }
comments(first: ${COMMENTS_PER_ISSUE}) {
nodes {
id
Expand DownExpand Up@@ -102,6 +103,9 @@ export type LinearIssueData = {
creator: LinearUserData | null;
assignee: LinearUserData | null;
state: { id: string; type: string } | null;
// The Linear project the issue belongs to (a container WITHIN a team —
// teams are already the sync channel). Feeds signals.taxonomy.
project?: { id: string } | null;
comments: { nodes: LinearCommentData[] };
attachments: { nodes: LinearAttachmentData[] };
};
Expand DownExpand Up@@ -227,6 +231,12 @@ export function buildIssueLink(
linearId: issue.id,
projectId,
},
// The issue's Linear project (a container within the team) as a
// namespaced taxonomy key — the platform matches it against priority
// bindings at L1. The team itself is the sync channel and needs no key.
...(issue.project?.id
? { signals: { taxonomy: [`linear:project:${issue.project.id}`] } }
: {}),
actions: threadActions.length > 0 ? threadActions : undefined,
sourceUrl: issue.url ?? null,
notes,
Expand Down
13 changes: 13 additions & 0 deletions twister/src/signals.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -81,6 +81,19 @@ export type LinkSignals = {
* platform classifies from `link.preview` instead.
*/
noteKey?: string | null;
/**
* Structured containers the source assigns this item to — the project,
* repository, board or folder the item lives in, as namespaced keys:
* `"<connector>:<kind>:<id>"` (e.g. `"linear:project:<uuid>"`,
* `"github:repo:<owner>/<name>"`).
*
* Use immutable ids (uuid/number), never mutable display names — the same
* rule as `link.source`. Emit every container the item genuinely belongs
* to; omit the field when the source has no structured container. The
* platform matches these keys exactly against priority bindings to route
* items to the user's priorities without a model call.
*/
taxonomy?: string[];
};

const NOREPLY_LOCALPART =
Expand Down
Loading