| 33 |
33
|
|
|
| 34 |
34
|
|
## Where they actually collide
|
| 35 |
35
|
|
|
| 36 |
|
- |
Three places, and only three. Everything else is a false conflict.
|
|
36
|
+ |
Three places. The owner's ruling on all three is the same and it is the
|
|
37
|
+ |
strict one:
|
| 37 |
38
|
|
|
| 38 |
|
- |
### 1. Status: two categories versus six
|
|
39
|
+ |
> I do not want custom fields. I would rather drop Linear concepts than munge
|
|
40
|
+ |
> them onto GitHub. Let us do GitHub API things only.
|
| 39 |
41
|
|
|
| 40 |
|
- |
GitHub's issue has `state`, and it is binary — `open` or `closed` — with
|
| 41 |
|
- |
`state_reason` narrowing a close (`completed`, `not_planned`, `duplicate`,
|
| 42 |
|
- |
`reopened`). Circle's issue has six: backlog, todo, in progress, in review,
|
| 43 |
|
- |
done, cancelled.
|
|
42
|
+ |
So the resolution below is not "find a GitHub-shaped home for every Linear
|
|
43
|
+ |
idea." It is: **if GitHub has no field for it, we do not have the concept.**
|
| 44 |
44
|
|
|
| 45 |
|
- |
The temptation is to add a `status` column. That breaks parity the moment a
|
| 46 |
|
- |
client reads it, because the client asked for a GitHub issue.
|
|
45
|
+ |
### 1. Status: two categories, not six
|
| 47 |
46
|
|
|
| 48 |
|
- |
**GitHub has already solved this, and recently.** The current issue schema
|
| 49 |
|
- |
carries `issue_field_values`, an array of typed custom fields:
|
|
47
|
+ |
GitHub's issue has `state` — `open` or `closed` — with `state_reason`
|
|
48
|
+ |
narrowing a close (`completed`, `not_planned`, `duplicate`, `reopened`).
|
|
49
|
+ |
Circle has six categories.
|
| 50 |
50
|
|
|
| 51 |
|
- |
```
|
| 52 |
|
- |
issue-field-value: issue_field_id, issue_field_name, data_type, value,
|
| 53 |
|
- |
single_select_option, multi_select_options
|
| 54 |
|
- |
data_type: text | single_select | multi_select | number | date
|
| 55 |
|
- |
```
|
|
51
|
+ |
We take GitHub's two. An issue is open or it is closed, and a closed one may
|
|
52
|
+ |
say why. Backlog, todo, in progress and in review are **not modelled**. They
|
|
53
|
+ |
are not renamed, not approximated by labels, and not stored anywhere.
|
| 56 |
54
|
|
|
| 57 |
|
- |
A `single_select` field named Status, whose options are the six categories, is
|
| 58 |
|
- |
**exactly** how GitHub models this today. It is not a workaround. We already
|
| 59 |
|
- |
have `project_fields` with `name`, `data_type` and `options`, which is the same
|
| 60 |
|
- |
shape one level up.
|
|
55
|
+ |
What survives is the *rendering*: `issue_status/1` draws a ring for open and a
|
|
56
|
+ |
filled check for closed, which is a better-looking pair of glyphs than the two
|
|
57
|
+ |
we had, and costs nothing in the contract.
|
| 61 |
58
|
|
|
| 62 |
|
- |
The mapping that keeps both promises:
|
|
59
|
+ |
A note for later, recorded because it will come up: the current GitHub schema
|
|
60
|
+ |
does carry `issue_field_values` — typed custom fields with `single_select`
|
|
61
|
+ |
among the data types — and that is genuinely how GitHub itself models a
|
|
62
|
+ |
richer status today. It was the obvious answer and the owner has declined it.
|
|
63
|
+ |
The reason is worth keeping: a field that only our UI writes and only our UI
|
|
64
|
+ |
reads is a second model wearing the first one's clothes, and every client that
|
|
65
|
+ |
does not know about it sees an issue that is subtly wrong. Two honest states
|
|
66
|
+ |
beat six states that only one client understands.
|
| 63 |
67
|
|
|
| 64 |
|
- |
| Reading | Writing |
|
| 65 |
|
- |
| --- | --- |
|
| 66 |
|
- |
| `state` stays derived: anything but done/cancelled is `open` | a client writing `state: "closed"` sets the Status field to done |
|
| 67 |
|
- |
| `state_reason` derives too: cancelled → `not_planned` | a client writing `state_reason: "not_planned"` sets cancelled |
|
| 68 |
|
- |
| the Status field is the richer truth | the UI writes the field; `state` follows |
|
|
68
|
+ |
### 2. Priority: dropped
|
| 69 |
69
|
|
|
| 70 |
|
- |
A GitHub client sees an issue that opens and closes. A person sees six
|
| 71 |
|
- |
categories. Neither is being lied to, because `open` genuinely is "not done and
|
| 72 |
|
- |
not cancelled".
|
|
70
|
+ |
GitHub has no priority field. We do not add one, and we do not adopt the
|
|
71
|
+ |
`priority: high` label convention either — a label is a flat, user-defined
|
|
72
|
+ |
tag, and an ordered scale pushed through it is a convention, not a contract.
|
| 73 |
73
|
|
|
| 74 |
|
- |
### 2. Priority: GitHub has no such field
|
| 75 |
|
- |
|
| 76 |
|
- |
Circle has four levels. GitHub has none — priority is conventionally a label
|
| 77 |
|
- |
(`priority: high`), which is why every GitHub-shaped tool grew a label
|
| 78 |
|
- |
convention.
|
| 79 |
|
- |
|
| 80 |
|
- |
Same answer, and it is the better one: a `single_select` custom field named
|
| 81 |
|
- |
Priority. Labels stay labels — a flat, user-defined vocabulary — instead of
|
| 82 |
|
- |
being overloaded into an ordered scale they cannot express. `issue_priority/1`
|
| 83 |
|
- |
already renders four levels from one shape, so the UI is waiting on the field,
|
| 84 |
|
- |
not the other way round.
|
| 85 |
|
- |
|
| 86 |
|
- |
If a client expects the label convention, emit **both**: the field is the
|
| 87 |
|
- |
truth, a `priority: high` label is a projection for tools that only read
|
| 88 |
|
- |
labels. Projections are cheap; a second source of truth is not.
|
|
74
|
+ |
`issue_priority/1` stays in the component library unused, the way
|
|
75
|
+ |
`pricing_column/1` does. When there is a GitHub-shaped place for it, the
|
|
76
|
+ |
renderer is ready. Until then the column simply is not there.
|
| 89 |
77
|
|
|
| 90 |
78
|
|
### 3. Grouping and cycles
|
| 91 |
79
|
|
|
| 92 |
|
- |
Circle groups by status, assignee, priority, or project, and has cycles.
|
| 93 |
|
- |
GitHub's list endpoint filters (`state`, `labels`, `assignee`, `milestone`,
|
| 94 |
|
- |
`since`) but does not group, and has milestones rather than cycles.
|
|
80
|
+ |
Grouping is free and we take it. The list endpoint returns issues; the page
|
|
81
|
+ |
groups them by state, assignee, label, or milestone. No parameter is added to
|
|
82
|
+ |
a GitHub-shaped endpoint, and no data changes — grouping is arithmetic over a
|
|
83
|
+ |
response.
|
| 95 |
84
|
|
|
| 96 |
|
- |
Grouping is a **view** concern: the list endpoint returns issues, the page
|
| 97 |
|
- |
groups them. Nothing to reconcile — do not add a `group_by` parameter to a
|
| 98 |
|
- |
GitHub-shaped endpoint.
|
| 99 |
|
- |
|
| 100 |
|
- |
Cycles are not milestones and should not pretend to be. A milestone has a due
|
| 101 |
|
- |
date and a fixed scope; a cycle is a repeating window that rolls unfinished
|
| 102 |
|
- |
work forward. Until there is a reason to model cycles, milestones are the
|
| 103 |
|
- |
GitHub-shaped answer and the UI should say "milestone".
|
|
85
|
+ |
Cycles are dropped. A milestone has a due date and a fixed scope; a cycle is a
|
|
86
|
+ |
repeating window that rolls unfinished work forward. GitHub has the first, so
|
|
87
|
+ |
we have the first, and the UI calls it a milestone.
|
| 104 |
88
|
|
|
| 105 |
89
|
|
## What GitHub gives us that Linear's model does not
|
| 106 |
90
|
|
|
| 107 |
|
- |
Worth noting, because the parity constraint is not purely a tax. The current
|
|
91
|
+ |
Worth stating, because the constraint is not only subtractive. The current
|
| 108 |
92
|
|
schema also carries:
|
| 109 |
93
|
|
|
| 110 |
94
|
|
- **`type`** — an issue type (bug, feature, task) with a colour, enabled per
|