@kurtbruns/kestrel 1.12.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +137 -1
- package/README.md +1 -1
- package/bin/kestrel.mjs +17 -2
- package/cli/api.mjs +462 -0
- package/cli/credentials.mjs +192 -0
- package/cli/dev-api.mjs +17 -11
- package/cli/login.mjs +155 -0
- package/cli/oauth.mjs +368 -0
- package/cli/openapi.mjs +569 -0
- package/cli/seed.mjs +1 -1
- package/dist/public/dashboard/app.js +350 -295
- package/dist/public/dashboard/app.js.map +4 -4
- package/dist/public/dashboard/index.html +2 -2
- package/dist/public/dashboard/styles.css +466 -153
- package/dist/worker/index.js +10634 -5773
- package/docs/README.md +3 -2
- package/docs/get-started/02-deploy.md +18 -21
- package/docs/get-started/03-access.md +10 -8
- package/docs/get-started/04-ses.md +2 -2
- package/docs/get-started/05-verify.md +4 -4
- package/docs/get-started/06-go-live.md +3 -2
- package/docs/guides/01-allow-claude.md +68 -0
- package/docs/guides/02-automate.md +88 -0
- package/docs/guides/09-as-your-list-grows.md +8 -4
- package/docs/publishing/01-your-list.md +38 -33
- package/docs/publishing/02-sending-rules.md +3 -3
- package/docs/publishing/03-name-your-publication.md +1 -1
- package/docs/publishing/04-write-a-post.md +15 -52
- package/docs/publishing/06-troubleshooting.md +30 -14
- package/docs/publishing/08-connect-claude.md +60 -0
- package/docs/publishing/09-share-your-subscribe-link.md +3 -3
- package/docs/publishing/10-check-your-setup.md +9 -16
- package/docs/publishing/11-email-template.md +3 -1
- package/docs/publishing/README.md +2 -2
- package/docs/reference/01-configuration.md +132 -85
- package/docs/reference/02-sending-domain-dns.md +2 -2
- package/migrations/0012_unsubscribe_cause.sql +139 -0
- package/migrations/0013_who_acted_client.sql +20 -0
- package/migrations/0014_send_acts.sql +25 -0
- package/package.json +1 -1
- package/template/README.md +2 -0
- package/template/claude/CLAUDE.md +18 -14
- package/template/claude/skills/migrate-list/SKILL.md +5 -4
- package/docs/guides/01-connect-claude.md +0 -98
- package/docs/publishing/07-working-with-claude.md +0 -30
|
@@ -88,6 +88,9 @@
|
|
|
88
88
|
/* Code surface — inline `code` chip and the fenced-block band. A hair off the editor well
|
|
89
89
|
(--syntax-bg), lighter than the neutral --chip, so code reads as code without a heavy box. */
|
|
90
90
|
--syntax-code-bg: #f2f2f4;
|
|
91
|
+
/* A doc's inline `code`: a maroon ink on the --chip surface, so a name in a sentence reads
|
|
92
|
+
apart from the prose around it and from a link's blue. */
|
|
93
|
+
--code-inline-fg: #9b2c2c;
|
|
91
94
|
/* HTTP method badges in the API reference (DESIGN §8). A self-contained palette like
|
|
92
95
|
--syntax-*: verb colors are a naming convention, not feedback or lifecycle, so they
|
|
93
96
|
borrow no semantic scale. The badge tints each over a wash of itself (.api-method). */
|
|
@@ -161,6 +164,7 @@
|
|
|
161
164
|
--syntax-emphasis: #f472b6;
|
|
162
165
|
--syntax-quote: #f0a86a;
|
|
163
166
|
--syntax-code-bg: #26262b;
|
|
167
|
+
--code-inline-fg: #f07a7a;
|
|
164
168
|
--method-get: #4ade80;
|
|
165
169
|
--method-post: #60a5fa;
|
|
166
170
|
--method-put: #fbbf24;
|
|
@@ -1854,8 +1858,10 @@ td a:hover {
|
|
|
1854
1858
|
.dash-drafts {
|
|
1855
1859
|
table-layout: fixed;
|
|
1856
1860
|
}
|
|
1861
|
+
/* Wide enough for the longest "edited <date>", a two-digit day and hour included, with the
|
|
1862
|
+
cell's padding: the fixed layout clips anything wider rather than growing the column. */
|
|
1857
1863
|
.dash-drafts td.edited {
|
|
1858
|
-
width:
|
|
1864
|
+
width: 15em;
|
|
1859
1865
|
white-space: nowrap;
|
|
1860
1866
|
}
|
|
1861
1867
|
@media (min-width: 721px) {
|
|
@@ -1867,13 +1873,15 @@ td a:hover {
|
|
|
1867
1873
|
.list-table .c-act {
|
|
1868
1874
|
width: 56px;
|
|
1869
1875
|
}
|
|
1870
|
-
/* Subscribers:
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
width: 152px;
|
|
1874
|
-
}
|
|
1876
|
+
/* Subscribers: a floor that keeps the email column about 250px beside its two others. With
|
|
1877
|
+
only Status and Joined beside an address, which rarely needs more than half the row, fixed
|
|
1878
|
+
widths left one wide gap before them; a share of the row each spreads the space evenly. */
|
|
1875
1879
|
.list-table.subs-table {
|
|
1876
|
-
min-width:
|
|
1880
|
+
min-width: 610px;
|
|
1881
|
+
}
|
|
1882
|
+
.list-table.subs-table .c-status,
|
|
1883
|
+
.list-table.subs-table .c-date {
|
|
1884
|
+
width: 24%;
|
|
1877
1885
|
}
|
|
1878
1886
|
|
|
1879
1887
|
.dash-section table {
|
|
@@ -1934,20 +1942,143 @@ th.num .th-sort {
|
|
|
1934
1942
|
cursor: default;
|
|
1935
1943
|
}
|
|
1936
1944
|
|
|
1937
|
-
/*
|
|
1938
|
-
|
|
1945
|
+
/* A route's or an object's sections (query, body, returns, refusals, fields) as Stripe's and
|
|
1946
|
+
Mux's references set theirs: a heading over a rule, then unboxed rows, each with the name,
|
|
1947
|
+
its type, and Required on one line and what it means under it, so a long name never
|
|
1948
|
+
squeezes its description. A name breaks only where its words join (the <wbr> the page puts
|
|
1949
|
+
after each _ . /). */
|
|
1950
|
+
.block.api-sec {
|
|
1951
|
+
margin-top: 22px;
|
|
1952
|
+
}
|
|
1953
|
+
.api-sec > .block-head {
|
|
1939
1954
|
margin: 0;
|
|
1955
|
+
padding-bottom: 8px;
|
|
1956
|
+
border-bottom: 1px solid var(--line-soft);
|
|
1940
1957
|
}
|
|
1941
|
-
.api-
|
|
1942
|
-
|
|
1958
|
+
.api-sec > .block-head .block-label {
|
|
1959
|
+
font-size: 15px;
|
|
1960
|
+
font-weight: 600;
|
|
1961
|
+
color: var(--fg);
|
|
1962
|
+
}
|
|
1963
|
+
.api-sec-meta {
|
|
1964
|
+
margin-left: 2px;
|
|
1965
|
+
font: 400 12px var(--mono);
|
|
1966
|
+
color: var(--muted);
|
|
1967
|
+
}
|
|
1968
|
+
/* Rules divide rows; the last row in a section has none under it. */
|
|
1969
|
+
.api-row,
|
|
1970
|
+
.api-lrow {
|
|
1971
|
+
padding: 11px 0;
|
|
1972
|
+
border-bottom: 1px solid var(--line);
|
|
1973
|
+
}
|
|
1974
|
+
.api-row:last-child,
|
|
1975
|
+
.api-lrow:last-child {
|
|
1976
|
+
border-bottom: 0;
|
|
1977
|
+
}
|
|
1978
|
+
.api-row-head {
|
|
1979
|
+
display: flex;
|
|
1980
|
+
align-items: baseline;
|
|
1981
|
+
flex-wrap: wrap;
|
|
1982
|
+
gap: 8px;
|
|
1983
|
+
}
|
|
1984
|
+
code.api-row-name {
|
|
1943
1985
|
font-size: 13px;
|
|
1944
|
-
|
|
1986
|
+
font-weight: 600;
|
|
1987
|
+
overflow-wrap: break-word;
|
|
1945
1988
|
}
|
|
1946
|
-
.api-
|
|
1947
|
-
|
|
1948
|
-
|
|
1989
|
+
.api-row-type {
|
|
1990
|
+
font: 12px var(--mono);
|
|
1991
|
+
color: var(--muted);
|
|
1992
|
+
}
|
|
1993
|
+
/* A type that names an object links to it, and reads as a link. */
|
|
1994
|
+
.api-row-type a {
|
|
1995
|
+
color: var(--fg);
|
|
1996
|
+
text-decoration: underline;
|
|
1997
|
+
text-underline-offset: 2px;
|
|
1998
|
+
}
|
|
1999
|
+
.api-row-desc {
|
|
2000
|
+
margin-top: 3px;
|
|
2001
|
+
font-size: 13.5px;
|
|
2002
|
+
}
|
|
2003
|
+
/* The refusals and the statuses are a ledger: the status in a column of its own, tinted by
|
|
2004
|
+
its class, so a reader scans by status. */
|
|
2005
|
+
.api-lrow {
|
|
2006
|
+
display: grid;
|
|
2007
|
+
grid-template-columns: 44px minmax(0, 1fr);
|
|
2008
|
+
gap: 6px;
|
|
2009
|
+
}
|
|
2010
|
+
.api-st {
|
|
2011
|
+
font: 600 12.5px / 20px var(--mono);
|
|
2012
|
+
color: var(--muted);
|
|
2013
|
+
}
|
|
2014
|
+
.api-st-4xx {
|
|
2015
|
+
color: var(--warn-fg);
|
|
2016
|
+
}
|
|
2017
|
+
.api-st-5xx {
|
|
2018
|
+
color: var(--danger-fg);
|
|
2019
|
+
}
|
|
2020
|
+
.api-st-2xx {
|
|
2021
|
+
color: var(--ok-fg);
|
|
2022
|
+
}
|
|
2023
|
+
.api-with {
|
|
2024
|
+
margin-top: 2px;
|
|
2025
|
+
font-size: 12px;
|
|
2026
|
+
color: var(--muted);
|
|
2027
|
+
}
|
|
2028
|
+
/* A status's name is words, not a code. */
|
|
2029
|
+
.api-statuses code.api-row-name {
|
|
2030
|
+
font-family: inherit;
|
|
2031
|
+
}
|
|
2032
|
+
/* A value's short cells beside its name (a condition kind's severity and fields), each led by
|
|
2033
|
+
its column's name, and the label of a later long line ("Remedy:"). */
|
|
2034
|
+
.api-row-meta {
|
|
2035
|
+
font-size: 12.5px;
|
|
2036
|
+
color: var(--muted);
|
|
2037
|
+
}
|
|
2038
|
+
.api-row-key {
|
|
2039
|
+
font-size: 12px;
|
|
2040
|
+
font-weight: 600;
|
|
2041
|
+
color: var(--muted);
|
|
2042
|
+
}
|
|
2043
|
+
/* What every route shares, described once ahead of the routes: concept prose, an object's
|
|
2044
|
+
description, an enum's values under a field's line, and the refusal table's next moves. */
|
|
2045
|
+
/* The reference's paragraphs, each with space below it, as a doc page's are. */
|
|
2046
|
+
.api-prose {
|
|
2047
|
+
margin: 0 0 10px;
|
|
2048
|
+
font-size: 14px;
|
|
2049
|
+
}
|
|
2050
|
+
/* A list in the reference's prose (the status ranges under Requests), set like its paragraphs. */
|
|
2051
|
+
.prose-list {
|
|
2052
|
+
padding-left: 20px;
|
|
2053
|
+
}
|
|
2054
|
+
.prose-list li {
|
|
2055
|
+
margin: 6px 0;
|
|
2056
|
+
}
|
|
2057
|
+
.api-values {
|
|
2058
|
+
margin: 4px 0 0;
|
|
2059
|
+
padding-left: 16px;
|
|
2060
|
+
}
|
|
2061
|
+
.api-values li {
|
|
2062
|
+
margin: 2px 0;
|
|
2063
|
+
}
|
|
2064
|
+
/* The name of a group an enum's values fall into, over its bullets. */
|
|
2065
|
+
.api-values-group {
|
|
2066
|
+
margin: 8px 0 0;
|
|
2067
|
+
font-size: 12.5px;
|
|
2068
|
+
font-weight: 600;
|
|
2069
|
+
color: var(--muted);
|
|
2070
|
+
}
|
|
2071
|
+
.api-returns {
|
|
2072
|
+
margin: 4px 0 6px;
|
|
2073
|
+
font-size: 13.5px;
|
|
2074
|
+
}
|
|
2075
|
+
/* A refusal's next move, under what it means: quieter, the step a client takes from it. */
|
|
2076
|
+
.api-move-next {
|
|
2077
|
+
display: block;
|
|
2078
|
+
margin-top: 2px;
|
|
2079
|
+
font-size: 12.5px;
|
|
2080
|
+
color: var(--muted);
|
|
1949
2081
|
}
|
|
1950
|
-
|
|
1951
2082
|
/* editor header */
|
|
1952
2083
|
.editor-head {
|
|
1953
2084
|
display: flex;
|
|
@@ -2075,6 +2206,10 @@ body.web-mode .content {
|
|
|
2075
2206
|
gap: 8px;
|
|
2076
2207
|
flex-wrap: wrap;
|
|
2077
2208
|
}
|
|
2209
|
+
/* Who acted on the send, a line under when it was sent (SPEC §11). */
|
|
2210
|
+
.rec-by {
|
|
2211
|
+
margin-top: 2px;
|
|
2212
|
+
}
|
|
2078
2213
|
/* Names the living layer of the record: the outcome tiles below keep moving as receipts
|
|
2079
2214
|
arrive, while the audience at fire and the published post are fixed (SPEC §8). */
|
|
2080
2215
|
.rec-tiles-cap {
|
|
@@ -2242,7 +2377,7 @@ body.web-mode .content {
|
|
|
2242
2377
|
font-size: 22px;
|
|
2243
2378
|
letter-spacing: -0.01em;
|
|
2244
2379
|
}
|
|
2245
|
-
/* The derived-phase pill (progressing / retrying /
|
|
2380
|
+
/* The derived-phase pill (progressing / retrying / backing_off / needs_attention /
|
|
2246
2381
|
settling / …). Tones reuse the status + semantic palettes so a phase reads the same
|
|
2247
2382
|
hue as its badge elsewhere. A plain label, so the default cursor (DESIGN §11). */
|
|
2248
2383
|
.phase-pill {
|
|
@@ -2801,6 +2936,14 @@ body.web-mode .content {
|
|
|
2801
2936
|
each state too, so the cue survives a reader who can't tell the dot's color. The
|
|
2802
2937
|
"unsaved" cue reads the same amber as the shared save bar so both save models feel
|
|
2803
2938
|
like one app. :empty (locked posts, which can't autosave) collapses it out. */
|
|
2939
|
+
/* A scheduled send never tested, said quietly beside the control that settles it (SPEC §8). */
|
|
2940
|
+
.test-hint {
|
|
2941
|
+
font-size: 13px;
|
|
2942
|
+
color: var(--muted);
|
|
2943
|
+
}
|
|
2944
|
+
.test-row {
|
|
2945
|
+
gap: 1rem;
|
|
2946
|
+
}
|
|
2804
2947
|
.save-status {
|
|
2805
2948
|
display: inline-flex;
|
|
2806
2949
|
align-items: center;
|
|
@@ -3328,6 +3471,13 @@ textarea.is-invalid {
|
|
|
3328
3471
|
font-size: 12.5px;
|
|
3329
3472
|
color: var(--status-scheduled-fg);
|
|
3330
3473
|
}
|
|
3474
|
+
/* Who scheduled the send and who last moved it (SPEC §11), under the time, in the banner's
|
|
3475
|
+
violet: a fact about the send, not a note asking for a look. */
|
|
3476
|
+
.sched-by {
|
|
3477
|
+
margin-top: 4px;
|
|
3478
|
+
font-size: 12.5px;
|
|
3479
|
+
color: var(--status-scheduled-fg);
|
|
3480
|
+
}
|
|
3331
3481
|
/* The Email only box under a dialog's time: the label, then its helper beneath. */
|
|
3332
3482
|
.listing-check {
|
|
3333
3483
|
display: flex;
|
|
@@ -3782,52 +3932,28 @@ textarea.is-invalid {
|
|
|
3782
3932
|
.doc code {
|
|
3783
3933
|
font-family: var(--mono);
|
|
3784
3934
|
font-size: 13px;
|
|
3785
|
-
|
|
3935
|
+
color: var(--code-inline-fg);
|
|
3936
|
+
background: var(--chip);
|
|
3786
3937
|
border: 1px solid var(--line);
|
|
3787
3938
|
border-radius: 4px;
|
|
3788
3939
|
padding: 1px 5px;
|
|
3789
3940
|
}
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3941
|
+
/* Bold in a doc sits between semibold and bold, the weight of its headings: the browser's
|
|
3942
|
+
`bolder` makes it 700, which outweighs the headings (650) on the system font. */
|
|
3943
|
+
.doc strong {
|
|
3944
|
+
font-weight: 650;
|
|
3945
|
+
}
|
|
3946
|
+
/* Code in a bold lead-in keeps its own weight: the chip already sets it apart. */
|
|
3947
|
+
.doc strong code {
|
|
3948
|
+
font-weight: normal;
|
|
3798
3949
|
}
|
|
3950
|
+
/* The inline-code chip above stays off the code in a block. */
|
|
3799
3951
|
.doc pre code {
|
|
3952
|
+
color: inherit;
|
|
3800
3953
|
background: none;
|
|
3801
3954
|
border: 0;
|
|
3802
3955
|
padding: 0;
|
|
3803
|
-
|
|
3804
|
-
/* Copy button on code blocks — revealed on hover (always shown on touch). */
|
|
3805
|
-
.code-copy {
|
|
3806
|
-
position: absolute;
|
|
3807
|
-
top: 8px;
|
|
3808
|
-
right: 8px;
|
|
3809
|
-
font: 11px var(--mono);
|
|
3810
|
-
padding: 3px 8px;
|
|
3811
|
-
border: 1px solid var(--line);
|
|
3812
|
-
border-radius: 5px;
|
|
3813
|
-
background: var(--card);
|
|
3814
|
-
color: var(--muted);
|
|
3815
|
-
cursor: pointer;
|
|
3816
|
-
opacity: 0;
|
|
3817
|
-
transition: opacity 0.12s;
|
|
3818
|
-
}
|
|
3819
|
-
.doc pre:hover .code-copy,
|
|
3820
|
-
.code-copy:focus-visible {
|
|
3821
|
-
opacity: 1;
|
|
3822
|
-
}
|
|
3823
|
-
.code-copy:hover {
|
|
3824
|
-
color: var(--fg);
|
|
3825
|
-
border-color: var(--muted);
|
|
3826
|
-
}
|
|
3827
|
-
@media (hover: none) {
|
|
3828
|
-
.code-copy {
|
|
3829
|
-
opacity: 1;
|
|
3830
|
-
}
|
|
3956
|
+
font-size: inherit;
|
|
3831
3957
|
}
|
|
3832
3958
|
/* Previous / Next pager at the foot of a doc — the sequential path, with titles. Two
|
|
3833
3959
|
bordered cards; a missing neighbour keeps its side empty so the lone card stays put. */
|
|
@@ -4016,16 +4142,27 @@ button.api-filter-clear svg {
|
|
|
4016
4142
|
color: var(--fg);
|
|
4017
4143
|
background: var(--hover);
|
|
4018
4144
|
}
|
|
4145
|
+
/* The reference reads as a document: a doc page's line height, not the browser's default. */
|
|
4019
4146
|
.api-content {
|
|
4020
4147
|
min-width: 0;
|
|
4021
|
-
max-width:
|
|
4148
|
+
max-width: 1440px;
|
|
4149
|
+
line-height: 1.5;
|
|
4150
|
+
}
|
|
4151
|
+
/* One reading measure for the page, a doc page's (720px): the intro, the topics, and each
|
|
4152
|
+
route's and object's prose and tables keep it, and only a route's examples, beside its
|
|
4153
|
+
prose on a wide screen, reach past it. */
|
|
4154
|
+
.api-head,
|
|
4155
|
+
.api-blurb,
|
|
4156
|
+
.api-section[data-tier="overview"] .api-res,
|
|
4157
|
+
.api-op-body {
|
|
4158
|
+
max-width: 720px;
|
|
4022
4159
|
}
|
|
4023
4160
|
.api-head {
|
|
4024
4161
|
margin: 0 0 12px;
|
|
4025
4162
|
}
|
|
4026
4163
|
.api-head h1 {
|
|
4027
4164
|
margin: 0 0 4px;
|
|
4028
|
-
font-size:
|
|
4165
|
+
font-size: 28px;
|
|
4029
4166
|
}
|
|
4030
4167
|
.api-section {
|
|
4031
4168
|
scroll-margin-top: calc(var(--bar-h) + 2px);
|
|
@@ -4036,35 +4173,53 @@ button.api-filter-clear svg {
|
|
|
4036
4173
|
.api-section.api-last {
|
|
4037
4174
|
min-height: calc(100vh - var(--bar-h));
|
|
4038
4175
|
}
|
|
4176
|
+
/* A tier is a page heading here, not the app's small-caps section label that h2 is elsewhere. */
|
|
4039
4177
|
.api-section h2 {
|
|
4040
|
-
font-size:
|
|
4041
|
-
|
|
4178
|
+
font-size: 24px;
|
|
4179
|
+
font-weight: 650;
|
|
4180
|
+
color: var(--fg);
|
|
4181
|
+
text-transform: none;
|
|
4182
|
+
letter-spacing: normal;
|
|
4183
|
+
margin: 28px 0 4px;
|
|
4184
|
+
}
|
|
4185
|
+
/* Where a jump from the rail or an address moves focus: a place to read from, not a control,
|
|
4186
|
+
so it takes no ring. */
|
|
4187
|
+
.api-section h2:focus,
|
|
4188
|
+
.api-res-head h3:focus {
|
|
4189
|
+
outline: none;
|
|
4042
4190
|
}
|
|
4043
4191
|
.api-blurb {
|
|
4044
|
-
margin: 0;
|
|
4192
|
+
margin: 0 0 10px;
|
|
4045
4193
|
font-size: 14px;
|
|
4046
|
-
max-width: 62ch;
|
|
4047
4194
|
}
|
|
4048
|
-
/* A resource: its heading
|
|
4195
|
+
/* A resource: its heading over a rule, scrolling with its routes. */
|
|
4049
4196
|
.api-res {
|
|
4050
4197
|
margin-top: 16px;
|
|
4051
4198
|
scroll-margin-top: var(--bar-h);
|
|
4052
4199
|
}
|
|
4200
|
+
/* A resource's heading, set like the page's other headings, flush with the text: it only
|
|
4201
|
+
pins under the bar, its vertical padding the band the rows scroll beneath. */
|
|
4053
4202
|
.api-res-head {
|
|
4054
|
-
position: sticky;
|
|
4055
|
-
top: var(--bar-h);
|
|
4056
|
-
z-index: var(--z-content);
|
|
4057
4203
|
display: flex;
|
|
4058
4204
|
align-items: center;
|
|
4059
4205
|
gap: 8px;
|
|
4060
|
-
|
|
4206
|
+
margin: 0;
|
|
4207
|
+
padding: 6px 0;
|
|
4208
|
+
border-bottom: 1px solid var(--line-soft);
|
|
4061
4209
|
background: var(--bg);
|
|
4062
|
-
border-radius: 6px;
|
|
4063
4210
|
}
|
|
4064
4211
|
|
|
4065
4212
|
.api-res-head h3 {
|
|
4066
4213
|
margin: 0;
|
|
4067
|
-
font-size:
|
|
4214
|
+
font-size: 20px;
|
|
4215
|
+
font-weight: 650;
|
|
4216
|
+
}
|
|
4217
|
+
/* A Getting started topic is a heading over prose, not a list's head: no rule, a step smaller. */
|
|
4218
|
+
.api-section[data-tier="overview"] .api-res-head {
|
|
4219
|
+
border-bottom: none;
|
|
4220
|
+
}
|
|
4221
|
+
.api-section[data-tier="overview"] .api-res-head h3 {
|
|
4222
|
+
font-size: 18px;
|
|
4068
4223
|
}
|
|
4069
4224
|
.api-rows {
|
|
4070
4225
|
list-style: none;
|
|
@@ -4073,46 +4228,8 @@ button.api-filter-clear svg {
|
|
|
4073
4228
|
}
|
|
4074
4229
|
.api-rows > li {
|
|
4075
4230
|
border-bottom: 1px solid var(--line);
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
place to the full documentation. */
|
|
4079
|
-
.api-route-sum {
|
|
4080
|
-
display: grid;
|
|
4081
|
-
grid-template-columns: 62px minmax(0, max-content) minmax(0, 1fr) 20px;
|
|
4082
|
-
gap: 14px;
|
|
4083
|
-
align-items: center;
|
|
4084
|
-
padding: 9px 6px;
|
|
4085
|
-
border-radius: 6px;
|
|
4086
|
-
list-style: none;
|
|
4087
|
-
cursor: pointer;
|
|
4088
|
-
}
|
|
4089
|
-
.api-route-sum::-webkit-details-marker {
|
|
4090
|
-
display: none;
|
|
4091
|
-
}
|
|
4092
|
-
.api-route-sum:hover {
|
|
4093
|
-
background: var(--hover);
|
|
4094
|
-
}
|
|
4095
|
-
.api-route-line {
|
|
4096
|
-
color: var(--muted);
|
|
4097
|
-
font-size: 13.5px;
|
|
4098
|
-
white-space: nowrap;
|
|
4099
|
-
overflow: hidden;
|
|
4100
|
-
text-overflow: ellipsis;
|
|
4101
|
-
}
|
|
4102
|
-
.api-route[open] .api-route-line {
|
|
4103
|
-
visibility: hidden;
|
|
4104
|
-
}
|
|
4105
|
-
.api-chev {
|
|
4106
|
-
width: 6px;
|
|
4107
|
-
height: 6px;
|
|
4108
|
-
border-right: 1.6px solid var(--muted);
|
|
4109
|
-
border-bottom: 1.6px solid var(--muted);
|
|
4110
|
-
justify-self: center;
|
|
4111
|
-
transform: rotate(-45deg);
|
|
4112
|
-
transition: transform 0.15s;
|
|
4113
|
-
}
|
|
4114
|
-
.api-route[open] .api-chev {
|
|
4115
|
-
transform: rotate(45deg);
|
|
4231
|
+
/* A route reached from its address lands just under the bar, the rule above it hidden. */
|
|
4232
|
+
scroll-margin-top: calc(var(--bar-h) - 1px);
|
|
4116
4233
|
}
|
|
4117
4234
|
/* The verb as a tinted label: its own --method-* color as text over a wash of itself. */
|
|
4118
4235
|
.api-method {
|
|
@@ -4136,6 +4253,13 @@ button.api-filter-clear svg {
|
|
|
4136
4253
|
.api-method.m-DELETE {
|
|
4137
4254
|
--m: var(--method-delete);
|
|
4138
4255
|
}
|
|
4256
|
+
/* An object heads its resource as a row of its own, marked in the verb's column. */
|
|
4257
|
+
.api-method.m-OBJECT {
|
|
4258
|
+
text-transform: uppercase;
|
|
4259
|
+
color: var(--muted);
|
|
4260
|
+
background: var(--card);
|
|
4261
|
+
box-shadow: inset 0 0 0 1px var(--line-soft);
|
|
4262
|
+
}
|
|
4139
4263
|
.api-path {
|
|
4140
4264
|
font: 13.5px var(--mono);
|
|
4141
4265
|
overflow-wrap: anywhere;
|
|
@@ -4146,30 +4270,126 @@ button.api-filter-clear svg {
|
|
|
4146
4270
|
border-radius: 3px;
|
|
4147
4271
|
padding: 0 2px;
|
|
4148
4272
|
}
|
|
4149
|
-
.api-route-detail {
|
|
4150
|
-
padding: 2px 6px 18px 82px;
|
|
4151
|
-
}
|
|
4152
4273
|
.api-summary {
|
|
4153
|
-
margin:
|
|
4154
|
-
font-size:
|
|
4274
|
+
margin: 0 0 10px;
|
|
4275
|
+
font-size: 15px;
|
|
4155
4276
|
}
|
|
4156
4277
|
.api-desc {
|
|
4157
|
-
margin:
|
|
4278
|
+
margin: 0 0 10px;
|
|
4158
4279
|
font-size: 14px;
|
|
4159
4280
|
}
|
|
4160
|
-
/* A
|
|
4281
|
+
/* A field or parameter the request can't do without. */
|
|
4161
4282
|
.api-req {
|
|
4162
|
-
|
|
4163
|
-
font-
|
|
4164
|
-
color: var(--
|
|
4165
|
-
border: 1px solid var(--line);
|
|
4166
|
-
border-radius: 4px;
|
|
4167
|
-
padding: 0 4px;
|
|
4283
|
+
font-size: 11.5px;
|
|
4284
|
+
font-weight: 600;
|
|
4285
|
+
color: var(--warn-fg);
|
|
4168
4286
|
}
|
|
4169
4287
|
|
|
4170
4288
|
.api-empty {
|
|
4171
4289
|
margin: 20px 0;
|
|
4172
4290
|
}
|
|
4291
|
+
/* An object, first under the resource whose routes return it: a row like a route's, that
|
|
4292
|
+
opens to its fields and example. */
|
|
4293
|
+
/* A route or an object, laid out in full as Stripe and Mux lay theirs: its name and tools,
|
|
4294
|
+
its method and path, then what it does beside its examples on a wide screen, under them
|
|
4295
|
+
on a narrow one. The examples scroll with the page: pinned, one taller than the window
|
|
4296
|
+
(the send view's) would never show its end. */
|
|
4297
|
+
/* Each route and object its own block, with room around it so the page reads as one at a time. */
|
|
4298
|
+
.api-op {
|
|
4299
|
+
padding: 36px 0 40px;
|
|
4300
|
+
}
|
|
4301
|
+
.api-op-head {
|
|
4302
|
+
display: flex;
|
|
4303
|
+
flex-wrap: wrap;
|
|
4304
|
+
align-items: baseline;
|
|
4305
|
+
justify-content: space-between;
|
|
4306
|
+
gap: 8px 16px;
|
|
4307
|
+
}
|
|
4308
|
+
.api-op-head h4 {
|
|
4309
|
+
margin: 0;
|
|
4310
|
+
font-size: 18px;
|
|
4311
|
+
font-weight: 650;
|
|
4312
|
+
}
|
|
4313
|
+
.api-op-head h4:focus {
|
|
4314
|
+
outline: none;
|
|
4315
|
+
}
|
|
4316
|
+
/* A heading is a link to its own address, in the heading's own ink, with a link icon in the
|
|
4317
|
+
margin to its left while it is hovered or focused, as GitHub's headings show theirs. */
|
|
4318
|
+
a.api-self {
|
|
4319
|
+
position: relative;
|
|
4320
|
+
color: inherit;
|
|
4321
|
+
text-decoration: none;
|
|
4322
|
+
}
|
|
4323
|
+
a.api-self svg {
|
|
4324
|
+
position: absolute;
|
|
4325
|
+
left: -20px;
|
|
4326
|
+
top: 50%;
|
|
4327
|
+
width: 14px;
|
|
4328
|
+
height: 14px;
|
|
4329
|
+
transform: translateY(-50%);
|
|
4330
|
+
color: var(--muted);
|
|
4331
|
+
opacity: 0;
|
|
4332
|
+
}
|
|
4333
|
+
a.api-self:hover svg,
|
|
4334
|
+
a.api-self:focus-visible svg {
|
|
4335
|
+
opacity: 1;
|
|
4336
|
+
}
|
|
4337
|
+
.api-op-call {
|
|
4338
|
+
display: flex;
|
|
4339
|
+
align-items: center;
|
|
4340
|
+
gap: 8px;
|
|
4341
|
+
margin: 6px 0 10px;
|
|
4342
|
+
}
|
|
4343
|
+
.api-op-call .api-method {
|
|
4344
|
+
padding: 0 6px;
|
|
4345
|
+
}
|
|
4346
|
+
/* Copy sits right after the path it copies, an icon alone, so the two read as one. */
|
|
4347
|
+
button.copy-btn.api-copy-path {
|
|
4348
|
+
padding: 3px 5px;
|
|
4349
|
+
}
|
|
4350
|
+
|
|
4351
|
+
.api-op-body {
|
|
4352
|
+
display: grid;
|
|
4353
|
+
gap: 4px 28px;
|
|
4354
|
+
}
|
|
4355
|
+
.api-op-text,
|
|
4356
|
+
.api-op-code {
|
|
4357
|
+
min-width: 0;
|
|
4358
|
+
}
|
|
4359
|
+
@media (min-width: 1280px) {
|
|
4360
|
+
.api-op-body {
|
|
4361
|
+
max-width: none;
|
|
4362
|
+
/* The examples take what the window has beyond the prose's measure, never under 360px. */
|
|
4363
|
+
grid-template-columns: minmax(0, 720px) minmax(360px, 1fr);
|
|
4364
|
+
}
|
|
4365
|
+
.api-op-code {
|
|
4366
|
+
align-self: start;
|
|
4367
|
+
}
|
|
4368
|
+
.api-op-code > .block:first-child {
|
|
4369
|
+
margin-top: 0;
|
|
4370
|
+
}
|
|
4371
|
+
}
|
|
4372
|
+
/* The rail lists every route by name under its resource, so it reads as the page's contents. */
|
|
4373
|
+
.api-nav-ops {
|
|
4374
|
+
display: none;
|
|
4375
|
+
}
|
|
4376
|
+
.api-nav-ops.open,
|
|
4377
|
+
.api-nav.filtering .api-nav-ops {
|
|
4378
|
+
display: flex;
|
|
4379
|
+
flex-direction: column;
|
|
4380
|
+
}
|
|
4381
|
+
.api-nav a.api-nav-op {
|
|
4382
|
+
padding-left: 30px;
|
|
4383
|
+
font-size: 13px;
|
|
4384
|
+
}
|
|
4385
|
+
.api-nav-tier + .api-nav-ops a.api-nav-op {
|
|
4386
|
+
padding-left: 20px;
|
|
4387
|
+
}
|
|
4388
|
+
.api-object {
|
|
4389
|
+
border-bottom: 1px solid var(--line);
|
|
4390
|
+
/* Reached from a route's Returns, it lands just under the bar. */
|
|
4391
|
+
scroll-margin-top: calc(var(--bar-h) - 1px);
|
|
4392
|
+
}
|
|
4173
4393
|
/* Reference room on mobile. The bar stays ONE row at --bar-h, the same as on desktop, so
|
|
4174
4394
|
the in-page anchors (scroll-margin-top) and the scroll-spy line keep clearing it: the back
|
|
4175
4395
|
link is a --bar-h square holding its arrow, the wordmark and the two tabs follow, and the
|
|
@@ -4284,11 +4504,13 @@ button.api-filter-clear svg {
|
|
|
4284
4504
|
font-size: 13px;
|
|
4285
4505
|
}
|
|
4286
4506
|
/* The API rail is the tier chips alone on a phone: its resources need the column the
|
|
4287
|
-
rail no longer has.
|
|
4507
|
+
rail no longer has. */
|
|
4288
4508
|
.api-filter {
|
|
4289
4509
|
max-width: none;
|
|
4290
4510
|
}
|
|
4291
|
-
.api-nav a.api-nav-res
|
|
4511
|
+
.api-nav a.api-nav-res,
|
|
4512
|
+
.api-nav .api-nav-ops,
|
|
4513
|
+
.api-nav.filtering .api-nav-ops {
|
|
4292
4514
|
display: none;
|
|
4293
4515
|
}
|
|
4294
4516
|
.api-nav a.api-nav-tier {
|
|
@@ -4297,25 +4519,6 @@ button.api-filter-clear svg {
|
|
|
4297
4519
|
.api-nav a.api-nav-tier ~ a.api-nav-tier {
|
|
4298
4520
|
margin-top: 0;
|
|
4299
4521
|
}
|
|
4300
|
-
.api-route-sum {
|
|
4301
|
-
grid-template-columns: 62px minmax(0, 1fr) 20px;
|
|
4302
|
-
gap: 4px 10px;
|
|
4303
|
-
}
|
|
4304
|
-
.api-route-line {
|
|
4305
|
-
grid-column: 2;
|
|
4306
|
-
grid-row: 2;
|
|
4307
|
-
white-space: normal;
|
|
4308
|
-
}
|
|
4309
|
-
.api-chev {
|
|
4310
|
-
grid-column: 3;
|
|
4311
|
-
grid-row: 1;
|
|
4312
|
-
}
|
|
4313
|
-
.api-route[open] .api-route-line {
|
|
4314
|
-
display: none;
|
|
4315
|
-
}
|
|
4316
|
-
.api-route-detail {
|
|
4317
|
-
padding: 0 4px 14px;
|
|
4318
|
-
}
|
|
4319
4522
|
/* inline-flex centres the chip's text in its 32px; the gap stands in for the space
|
|
4320
4523
|
between the label and its ↗ that flex drops. */
|
|
4321
4524
|
.rail-link {
|
|
@@ -4720,6 +4923,10 @@ button.copy-btn:hover {
|
|
|
4720
4923
|
button.copy-btn:active {
|
|
4721
4924
|
transform: none;
|
|
4722
4925
|
}
|
|
4926
|
+
/* A Copy that is its glyph alone (no word) takes no gap for the word. */
|
|
4927
|
+
button.copy-btn span:empty {
|
|
4928
|
+
display: none;
|
|
4929
|
+
}
|
|
4723
4930
|
button.copy-btn svg {
|
|
4724
4931
|
width: 14px;
|
|
4725
4932
|
height: 14px;
|
|
@@ -4734,12 +4941,55 @@ button.copied svg {
|
|
|
4734
4941
|
font-weight: 600;
|
|
4735
4942
|
color: var(--muted);
|
|
4736
4943
|
}
|
|
4737
|
-
|
|
4944
|
+
/* A code block (an example, a curl command, a template): a raised card with its label and Copy
|
|
4945
|
+
in a header inside it, so the code stands apart from the page in both themes. */
|
|
4946
|
+
.code-block {
|
|
4947
|
+
background: var(--card);
|
|
4948
|
+
border: 1px solid var(--line);
|
|
4949
|
+
border-radius: 8px;
|
|
4950
|
+
overflow: hidden;
|
|
4951
|
+
}
|
|
4952
|
+
.code-block > .block-head {
|
|
4738
4953
|
margin: 0;
|
|
4739
|
-
|
|
4740
|
-
|
|
4954
|
+
padding: 4px 6px 4px 12px;
|
|
4955
|
+
border-bottom: 1px solid var(--line);
|
|
4956
|
+
}
|
|
4957
|
+
/* A bare code block (a doc's): the same raised card with no label line, the code beside a
|
|
4958
|
+
Copy that is its glyph alone, level with the first line, so a one-line command reads as
|
|
4959
|
+
one quiet row. */
|
|
4960
|
+
.code-bare {
|
|
4961
|
+
display: flex;
|
|
4962
|
+
align-items: flex-start;
|
|
4963
|
+
gap: 8px;
|
|
4964
|
+
background: var(--card);
|
|
4741
4965
|
border: 1px solid var(--line);
|
|
4742
|
-
border-radius:
|
|
4966
|
+
border-radius: 8px;
|
|
4967
|
+
padding: 0 6px 0 0;
|
|
4968
|
+
}
|
|
4969
|
+
.code-bare pre {
|
|
4970
|
+
flex: 1;
|
|
4971
|
+
min-width: 0;
|
|
4972
|
+
margin: 0;
|
|
4973
|
+
overflow-x: auto;
|
|
4974
|
+
padding: 11px 0 11px 14px;
|
|
4975
|
+
font: 13px / 1.5 var(--mono);
|
|
4976
|
+
}
|
|
4977
|
+
.code-bare > button.copy-btn {
|
|
4978
|
+
flex: none;
|
|
4979
|
+
margin-top: 8px;
|
|
4980
|
+
padding: 5px;
|
|
4981
|
+
}
|
|
4982
|
+
.code-bare > button.copy-btn svg {
|
|
4983
|
+
width: 16px;
|
|
4984
|
+
height: 16px;
|
|
4985
|
+
}
|
|
4986
|
+
/* A doc's code block is spaced like a paragraph. */
|
|
4987
|
+
.doc .code-bare {
|
|
4988
|
+
margin: 14px 0;
|
|
4989
|
+
}
|
|
4990
|
+
.code-block pre {
|
|
4991
|
+
margin: 0;
|
|
4992
|
+
overflow-x: auto;
|
|
4743
4993
|
padding: 10px 12px;
|
|
4744
4994
|
font: 12.5px / 1.5 var(--mono);
|
|
4745
4995
|
}
|
|
@@ -6515,7 +6765,7 @@ body.has-savebar .content {
|
|
|
6515
6765
|
.subs-wrap {
|
|
6516
6766
|
container-type: inline-size;
|
|
6517
6767
|
}
|
|
6518
|
-
@container (max-width:
|
|
6768
|
+
@container (max-width: 609px) {
|
|
6519
6769
|
.list-table.subs-table.stacks,
|
|
6520
6770
|
.list-table.subs-table.stacks tbody,
|
|
6521
6771
|
.list-table.subs-table.stacks tr {
|
|
@@ -6556,10 +6806,7 @@ body.has-savebar .content {
|
|
|
6556
6806
|
.list-table.subs-table.stacks td.empty {
|
|
6557
6807
|
display: none;
|
|
6558
6808
|
}
|
|
6559
|
-
/* Subscribers: status pill ·
|
|
6560
|
-
.subs-table td.how::before {
|
|
6561
|
-
content: " · ";
|
|
6562
|
-
}
|
|
6809
|
+
/* Subscribers: status pill · joined <date>. */
|
|
6563
6810
|
.subs-table td.joined::before {
|
|
6564
6811
|
content: " · joined ";
|
|
6565
6812
|
}
|
|
@@ -6687,8 +6934,10 @@ body.has-savebar .content {
|
|
|
6687
6934
|
font-weight: 600;
|
|
6688
6935
|
margin: 14px 0 4px;
|
|
6689
6936
|
}
|
|
6690
|
-
/*
|
|
6691
|
-
|
|
6937
|
+
/* On Workers Free's limits, the advice to move to Workers Paid on a list past what Free suits,
|
|
6938
|
+
on the Dashboard, the Subscribers screen, and an import's plan (DESIGN §4): the same amber
|
|
6939
|
+
box the Schedule dialog gives D1's daily-writes warning. */
|
|
6940
|
+
.workers-plan {
|
|
6692
6941
|
color: var(--warn-fg);
|
|
6693
6942
|
background: var(--warn-bg);
|
|
6694
6943
|
border: 1px solid var(--warn-line);
|
|
@@ -6729,9 +6978,73 @@ body.has-savebar .content {
|
|
|
6729
6978
|
gap: 14px;
|
|
6730
6979
|
margin-top: 18px;
|
|
6731
6980
|
}
|
|
6732
|
-
/*
|
|
6981
|
+
/* A subscriber's page is titled with the address, so a long one breaks rather than widens
|
|
6982
|
+
the head; the status pill and the flag sit beside it, as the roster draws them. */
|
|
6733
6983
|
.sub-title {
|
|
6734
6984
|
overflow-wrap: anywhere;
|
|
6985
|
+
margin: 0;
|
|
6986
|
+
}
|
|
6987
|
+
.sub-page-title {
|
|
6988
|
+
display: flex;
|
|
6989
|
+
align-items: center;
|
|
6990
|
+
gap: 12px;
|
|
6991
|
+
flex-wrap: wrap;
|
|
6992
|
+
min-width: 0;
|
|
6993
|
+
}
|
|
6994
|
+
.sub-page-title .sub-pills {
|
|
6995
|
+
white-space: nowrap;
|
|
6996
|
+
}
|
|
6997
|
+
/* Whether they receive posts: the page's first answer, under the head, in the record's
|
|
6998
|
+
meta-line place; muted, with a green dot only when they do, as a confirmed pill is. */
|
|
6999
|
+
.sub-receives {
|
|
7000
|
+
margin: -6px 0 14px;
|
|
7001
|
+
font-size: 13.5px;
|
|
7002
|
+
color: var(--muted);
|
|
7003
|
+
}
|
|
7004
|
+
.sub-receives.yes::before {
|
|
7005
|
+
content: "";
|
|
7006
|
+
display: inline-block;
|
|
7007
|
+
width: 8px;
|
|
7008
|
+
height: 8px;
|
|
7009
|
+
margin-right: 7px;
|
|
7010
|
+
border-radius: 50%;
|
|
7011
|
+
background: var(--ok-fg);
|
|
7012
|
+
}
|
|
7013
|
+
/* The import note: the card's own small print, outside a dialog's hint styles. */
|
|
7014
|
+
.sub-note {
|
|
7015
|
+
margin: 10px 0 0;
|
|
7016
|
+
font-size: 12.5px;
|
|
7017
|
+
color: var(--muted);
|
|
7018
|
+
}
|
|
7019
|
+
.sub-sec .sub-meaning {
|
|
7020
|
+
margin: 8px 0;
|
|
7021
|
+
font-size: 13.5px;
|
|
7022
|
+
}
|
|
7023
|
+
.sub-sec p:last-child {
|
|
7024
|
+
margin-bottom: 0;
|
|
7025
|
+
}
|
|
7026
|
+
.sub-sup-line,
|
|
7027
|
+
.sub-left-line {
|
|
7028
|
+
margin: 0;
|
|
7029
|
+
}
|
|
7030
|
+
/* Sends is a table, and a table is already a card, so its section is no card of its own:
|
|
7031
|
+
the label sits on the page above it, as the dashboard's sections do. */
|
|
7032
|
+
.sub-sends {
|
|
7033
|
+
margin-top: 22px;
|
|
7034
|
+
}
|
|
7035
|
+
.sub-sends-table .c-out {
|
|
7036
|
+
width: 160px;
|
|
7037
|
+
}
|
|
7038
|
+
.sub-sends-table .sub-send a {
|
|
7039
|
+
font-weight: 500;
|
|
7040
|
+
}
|
|
7041
|
+
.sub-more {
|
|
7042
|
+
margin-top: 12px;
|
|
7043
|
+
}
|
|
7044
|
+
/* The roster's address is its row's link, as a Sent row's subject is, in the text's color
|
|
7045
|
+
since the whole row is the click target. */
|
|
7046
|
+
.subs-table a.sub-email {
|
|
7047
|
+
color: inherit;
|
|
6735
7048
|
}
|
|
6736
7049
|
.imp-dl,
|
|
6737
7050
|
.sub-dl {
|