From 0333a662cdfe564339a5379c0badc7847633e858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20Ainsworth?= Date: Mon, 16 Mar 2026 06:43:36 +0900 Subject: [PATCH 1/7] Subscriptions overhaul --- src/content/docs/specs/index.md | 17 + src/content/docs/specs/index.mdx | 37 -- src/content/docs/specs/subscriptions.md | 615 ++++++++++++++++++ .../docs/specs/subscriptions/add-new.mdx | 243 ------- .../docs/specs/subscriptions/delete.mdx | 82 --- .../docs/specs/subscriptions/get-all.mdx | 294 --------- .../docs/specs/subscriptions/get-single.mdx | 126 ---- .../docs/specs/subscriptions/index.mdx | 37 -- .../docs/specs/subscriptions/status.mdx | 137 ---- .../docs/specs/subscriptions/update.mdx | 127 ---- 10 files changed, 632 insertions(+), 1083 deletions(-) create mode 100644 src/content/docs/specs/index.md delete mode 100644 src/content/docs/specs/index.mdx create mode 100644 src/content/docs/specs/subscriptions.md delete mode 100644 src/content/docs/specs/subscriptions/add-new.mdx delete mode 100644 src/content/docs/specs/subscriptions/delete.mdx delete mode 100644 src/content/docs/specs/subscriptions/get-all.mdx delete mode 100644 src/content/docs/specs/subscriptions/get-single.mdx delete mode 100644 src/content/docs/specs/subscriptions/index.mdx delete mode 100644 src/content/docs/specs/subscriptions/status.mdx delete mode 100644 src/content/docs/specs/subscriptions/update.mdx diff --git a/src/content/docs/specs/index.md b/src/content/docs/specs/index.md new file mode 100644 index 00000000..3d3aee0e --- /dev/null +++ b/src/content/docs/specs/index.md @@ -0,0 +1,17 @@ +--- +title: API reference +description: The Open Podcast API is a standard that facilitates the synchronization of podcast data between podcast clients. +sidebar: + order: 1 +--- + +## 1. Overview + +The Open Podcast API is a standard that facilitates the synchronization of podcast data between podcast clients. +This specification aims to provide comprehensive instructions for client and server developers looking to support the standard. + +## 2. Definitions + +The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", and "MAY" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119). + +## 3. Authentication \ No newline at end of file diff --git a/src/content/docs/specs/index.mdx b/src/content/docs/specs/index.mdx deleted file mode 100644 index 4582eb28..00000000 --- a/src/content/docs/specs/index.mdx +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: API Specs -description: All supported API specifications -next: false -prev: false -tableOfContents: false -sidebar: - order: 1 ---- - -:::caution[Important] -All specifications are currently 'in progress'. Breaking changes can occur as we implement specifications and address issues. -::: - -Below you can find the specifications which are already available. We encourage all interested projects offering podcast listening and/or synchronization functionality to adopt and implement defined specifications. We also welcome feedback on these sepcifications. - -## Core and optional functionality - -To ensure that the end-user experience is consistent across implementations, the specifications mark endpoints and features as **Core** (required) and **Optional**. - -**Core** -: The feature or endpoint MUST be supported by all clients and servers. - -**Optional** -: The feature or endpoint is considered to be additional functionality. Clients and servers MAY optionally support any combination of these features. Any project implementing **Optional** functionality SHOULD inform users about what is supported. - -Which features a server supports MUST be exposed through a Capabilities endpoint. - -## Core endpoints - - - - diff --git a/src/content/docs/specs/subscriptions.md b/src/content/docs/specs/subscriptions.md new file mode 100644 index 00000000..f4b68251 --- /dev/null +++ b/src/content/docs/specs/subscriptions.md @@ -0,0 +1,615 @@ +--- +title: Subscription API specification +descriptions: Use the subscriptions endpoint to manage podcast subscriptions +sidebar: + label: Subscriptions + badge: + text: Experimental + variant: caution +banner: + content: This is a core endpoint. All implementing servers and clients MUST support it. +--- + +Subscriptions represent the relationship between a user and a podcast feed. + +## 1. Introduction + +Subscriptions are at the heart of the Open Podcast API. They represent which feeds a user has subscribed to, both presently and historically. + +The `subscriptions` endpoint is designed to give clients a simple interface for synchronizing a user's podcast subscriptions. It aims to support: + +* Offline-first operation +* Deterministic identifiers +* Idempotent operations +* Efficient incremental synchronization +* Multi-device consistency + +## 2. Motivation + +The Podcast 2.0 specification presents developers with stable identifiers (`podcast:guid`), which are UUIDv5 values that can be calculated from the feed URL using a standard-supplied namespace. However, the original podcast specification makes no such guarantees. This makes implementing cross-device synchronization difficult, as developers need to use unstable fields to determine which feed is being targeted. + +To resolve this, the Open Podcast API makes use of the same deterministic UUID resolution outlined in the Podcast Index documentation[^1] and requires Clients to provide a calculated UUID value with every feed. + +## 3. Conventions used in this document + +### 3.1 Normative language + +The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", and "MAY" in this document are to be interpreted as described in RFC 2119[^2]. + +The following terms are also used throughout this document: + +Client +: Software that sends HTTP requests to a conforming server. + +Server +: An implementation that exposes the endpoints defined in this specification. + +User +: The authenticated principal performing requests. + +Feed +: A shared resource representing a podcast feed. + +Subscription: A user-owned resource containing details about a User's subscription to a Feed. + +Action +: An operation performed against a subscription resource. + +Cursor +: An opaque token used to resume synchronization. + +### 3.2 Timestamp format + +Timestamps MUST be conform to RFC 3339[^3] and be submitted in UTC. + +### 3.3 Data Serialization + +All request and response bodies MUST be encoded as UTF-8 JSON. + +### 3.4 Identifier formats + +This specification uses the following identifier formats: + +* UUID version 4 for client identifiers as defined in RFC 9562[^4] +* UUID version 5 for deterministic resource identifiers as defined in RFC 9562[^4] +* Base64 encoding for cursors + +## 4. Scope + +This specification defines: + +* Resource identifiers +* Action submission semantics +* Synchronization mechanisms +* Conflict resolution rules +* Client and server behavior + +This document does not define: + +* User authentication mechanisms +* Feed metadata ingestion +* Client user interface behavior + +## 5. System Architecture + +### 5.1 Overview + +The system consists of: + +* Client devices +* An HTTP API server + +### 5.2 Offline Operation + +Clients MAY operate without network connectivity and queue actions locally. + +Queued actions MUST be transmitted to the server when connectivity is restored. + +### 5.3 Synchronization Model + +Synchronization is based on an append-only action log. + +Clients retrieve new actions using a cursor-based incremental synchronization mechanism. + +## 6. UUID calculation + +Feeds are identified using **deterministic UUIDv5 identifiers** derived from podcast feed URLs. +Clients MUST provide a valid UUIDv5 identifier for all feed objects. +This UUID value must be determined by ONE of the following methods, in order of preference: + +1. Using the `podcast:guid` value of the feed's RSS file, if it is a valid UUID OR, +1. Calculating a UUIDv5 value using the normalized `feed_url`. + +To calculate the UUID value, the client MUST do the following: + +1. Normalize the `feed_url` by removing the scheme (for example: `https://`) and all trailing slashes (`/`). +1. Calculate the UUID using the normalized `feed_url` and the podcast namespace UUID: `ead4c236-bf58-58c6-a2c6-a6b28d128cb6`. + +See the Podcast Index's `Guid` documentation for more information.[^1] + +### 6.1 Example + +```py +import uuid +import re + +def calculate_uuid(feed_url): + PODCAST_NAMESPACE = uuid.UUID("ead4c236-bf58-58c6-a2c6-a6b28d128cb6") + sanitized_feed_url = re.sub(r'^[a-zA-Z]+://', '', feed_url).rstrip('/') + return uuid.uuid5(PODCAST_NAMESPACE, sanitized_feed_url) +``` + +Running the above example with the feed URL `"https://podnews.net/rss/"` will yield `9b024349-ccf0-5f69-a609-6b82873eab3c`. + +## 7. Subscription status + +Subscriptions are considered valid even if the User has unsubscribed from the feed. Unsubscribing is a **non-destructive** action that leaves the subscription entry intact. + +A User is "subscribed" to a Feed when they: + +1. Have a Subscription entry for the Feed AND +1. The `unsubscribed_at` timestamp is null. + +Clients may submit an `update` to a Subscription with a null `unsubscribed_at` timestamp to resubscribe a user to a feed. + +## 7. Resource models + +### 7.1 Feed + +A Feed represents a shared logical resource corresponding to a podcast RSS feed. Feeds are uniquely identified by a deterministic UUID derived from the normalized feed URL and a podcast namespace UUID. + +A Feed resource MAY exist independently of any Subscriptions but MAY also be created implicitly when a Subscription is submitted. + +#### Fields + +| Field | Type | Required | Description | +| ------------ | ---------------- | -------- | ------------------------------------------------------- | +| `uuid` | UUID | Yes | Deterministic identifier for the feed | +| `feed_url` | string | Yes | The RSS feed's canonical URL used to calculate the UUID | +| `created_at` | string (RFC3339) | Yes | Server-authoritative creation timestamp | +| `updated_at` | string (RFC3339) | Yes | Server-authoritative update timestamp | + +### 8.2 Subscription + +A Subscription represents a user's subscription to a given Feed. + +Each User MAY have **at most one Subscription per Feed**. + +A Subscription is uniquely identified by the tuple: + +```txt +(user, feed_uuid) +``` + +Clients do not directly access Subscription identifiers. Subscriptions are accessed via the Feed resource. + +#### Fields + +| Field | Type | Required | Description | +| ----------------- | ---------------- | -------- | ------------------------------------------------------------------------------------------------ | +| `subscribed_at` | string (RFC3339) | Yes | Client-provided subscription timestamp, if submitted. Implicitly created by the server if absent | +| `unsubscribed_at` | string (RFC3339) | No | Client-provided unsubscription timestamp, if submitted. | +| `created_at` | string (RFC3339) | Yes | Server-authoritative creation timestamp | +| `updated_at` | string (RFC3339) | Yes | Server-authoritative update timestamp | + +Normative rule: `created_at` and `updated_at` are managed by the server. Clients MAY supply `subscribed_at` and `unsubscribed_at` in requests but it doesn't override the server’s canonical timestamps. + +## 8. Client action submission + +### 8.1 Endpoint + +```http +POST /api/v1/subscriptions +``` + +Clients use this endpoint to submit subscription actions. + +### 8.2 Purpose + +This endpoint supports the submission of `actions` for Subscriptions. Each `action` MUST reference a Feed. + +### 8.3 Supported actions + +Each object in a request payload MUST reference an `action`. The supported actions for this endpoint are: + +`create` +: Create a new subscription for the authenticated User and the referenced Feed + +`update` +: Update the subscription details for an authenticated User and a referenced Feed + +### 8.4 Response statuses + +Each handled item in a POST request to this endpoint MUST be returned in the response. To inform the Client, each object MUST contain a `status` field matching the following enumerable values: + +`created` +: The subscription was created successfully + +`updated` +: The subscription was updated successfully + +`conflict` +: A subscription for the requesting User to the provided Feed exists already + +`duplicate` +: The payload object is a duplicate of another update in the same payload + +`invalid_action` +: The payload object referenced an invalid [action](#83-supported-actions) + +`malformed_feed_uuid` +: The UUID value in the Feed payload is malformed + +`malformed_feed_url` +: The URL in the Feed payload is not a valid URI value + +`transient_server_error` +: The Server could not perform the update due to a transient issue such as database connection issues + +### 8.5 Request format + +Requests sent to this endpoint MUST conform to the following: + +1. All requests MUST be submitted as an array of objects, with at least one and at most 30 items. +1. Each item in the array MUST include all required fields. + +Servers MUST immediately reject any invalid payload with a `400` response. + +| Field | Type | Required | Description | +| ----------------------------- | ------------------------ | -------- | ---------------------------------------------------------------------------------- | +| `data` | array | Yes | The array of data submitted to the server | +| `data.uuid` | UUID | Yes | The Client-generated UUIDv4 identifier for the action | +| `data[].action` | string | Yes | The [supported action](#83-supported-actions) being taken against the subscription | +| `data[].feed` | object | Yes | Details about the Feed that the subscription targets | +| `data[].feed.uuid` | UUID | Yes | The calculated UUIDv5 identifier for the Feed | +| `data[].feed.feed_url` | string | Yes | The canonical URL of the feed RSS file | +| `data[].data` | object | Yes | The data object containing subscription information with **at least one** value | +| `data[].data.subscribed_at` | string (RFC3339) | No | The timestamp at which the subscription was created | +| `data[].data.unsubscribed_at` | string (RFC3339) or null | No | The timestamp at which the user unsubscribed from the feed | + + +### 8.6 Response format + +If all fields in the request payload are valid, the Server MUST respond with a `202` status and return a payload with an object corresponding to each `action` submitted. + +| Field | Type | Required | Description | +| --------------------------------------- | ---------------- | -------- | ----------------------------------------------------------------------- | +| `data` | array | Yes | The array of response objects | +| `data.uuid` | UUID | Yes | The Client-generated UUIDv4 identifier for the action | +| `data.status` | string | Yes | The Server-authoritative [response status](#84-response-statuses) | +| `data.received` | string (RFC3339) | Yes | The Server-authoritative timestamp at which the request was received | +| `data[].feed` | object | No | The referenced Feed item for the action | +| `data[].feed.uuid` | UUID | Yes | The calculated UUIDv5 identifier for the feed | +| `data[].feed.feed_url` | string | Yes | The canonical URL of the feed RSS file | +| `data[].feed.created_at` | string (RFC3339) | Yes | The Server-authoritative creation timestamp for the Feed entity | +| `data[].feed.updated_at` | string (RFC3339) | No | The Server-authoritative last update timestamp for the Feed entity | +| `data[].subscription` | object | No | The Subscription entity | +| `data[].subscription.subscribed_at` | string (RFC3339) | No | The timestamp at which the User subscribed to the Feed | +| `data[].subscription.unsubscribed_at` | string (RFC3339) | No | The timestamp at which the User subscribed to the Feed | +| `data[].subscription.created_at` | string (RFC3339) | Yes | The Server-authoritative creation timestamp for the Subscription entity | +| `data[].subscription.updated_at` | string (RFC3339) | Yes | The Server-authoritative update timestamp for the Subscription entity | + +### 8.7 Client behavior + +The Client MUST follow these rules when submitting a request to this endpoint: + +1. The Client MUST NOT submit more than 30 items in a single payload. +1. The Client MUST generate a random UUID for each action in the payload. +1. The Client MUST await a response from the Server before submitting a new request. +1. The Client SHOULD inform the User of any failures that were received in the response. +1. The Client MAY retry items that failed with a status of `transient_server_error`. +1. The Client MUST NOT retry items that failed with a status of `invalid_action`. +1. The Client MUST NOT retry items that failed with a status of `malformed_uuid`. +1. The Client MUST NOT retry items that failed with a status of `malformed_feed_url`. +1. The Client MAY use the `updated_at` timestamp of the Subscription to communicate to a user when the subscription was made active again. + +### 8.8 Server behavior + +The Server MUST keep all action requests in a centralized append-only log format. The Server MAY compact this data to retain only the latest action of a given type. + +The Server MUST update the materialized view of updated entities and return their data in response to updates. + +The Server MUST follow these rules when processing a request to this endpoint: + +1. The Server MUST respond with a `400` error if the payload doesn't contain all required fields. +1. The Server MUST respond with a `400` error if the payload contains **more than 30** or **fewer than 1** items. +1. The Server MUST NOT attempt to process any action that fails validation. +1. The Server MUST process all objects in the response and return a corresponding object in the response. +1. The Server MUST discard any duplicate object from the payload and process only one version of the `action`. +1. The Server MUST create a corresponding object for all submitted `actions` and respond with an array matching the length of the submission. +1. The Server MUST implicitly create a Feed for all actions that reference a non-extant Feed. + +For each Feed: + +1. The Server MUST generate a `created_at` timestamp recording the date and time at which the Feed was added to the system. +1. The Server MUST generate an `updated_at` timestamp recording the date and time at which the Feed was last modified. + +For each Subscription: + +1. The Server MUST generate a `created_at` timestamp recording the date and time at which the Subscription was added to the system. +1. The Server MUST generate an `updated_at` timestamp recording the date and time at which the Subscription was last modified. +1. The Server SHOULD generate a `subscribed_at` timestamp matching the `created_at` timestamp if no `subscribed_at` field is received in the creation payload. +1. The Server MUST NOT add an `unsubscribed_at` timestamp unless one is sent by the Client. + +### 8.9 Example + +```jsonc title="Request" +{ + "data": [ + // Subscribe to a feed + { + "uuid": "329e6b8f-a540-4c6e-9ba0-2996e0352736", + "action": "create", + "feed": { + "uuid": "2fa174b5-2cd8-5c07-b086-fc60045fd9bf", + "feed_url": "https://example.com/feed1.rss/" + }, + "data": { + "subscribed_at": "2026-03-16T05:20:48.000Z" + } + }, + + // Resubscribe to a feed + { + "uuid": "987f1cad-807f-4c00-88aa-277fd470697a", + "action": "update", + "feed": { + "uuid": "34a12041-bdcd-5a3a-be5e-657315db7c44", + "feed_url": "https://example.com/feed2.rss/" + }, + "data": { + "unsubscribed_at": null + } + }, + + // Unsubscribe from a feed + { + "uuid": "4dcf3a4a-42dd-4658-88f6-c71887a04bb8", + "action": "update", + "feed": { + "uuid": "fc4ed290-4621-54fe-b5b4-a001343aeed7", + "feed_url": "https://example.com/feed3.rss/" + }, + "data": { + "unsubscribed_at": "2026-03-16T05:21:48.000Z" + } + }, + + // Invalid action + { + "uuid": "100c7e48-085f-4906-a91e-40c3c4b1a73e", + "action": "unsupported", + "feed": { + "uuid": "4790ba1b-1d4e-5f24-886e-7359eb98d52d", + "feed_url": "https://example.com/feed4.rss/" + }, + "data": { + "subscribed_at": "2026-03-16T06:00:02.000Z", + } + }, + + // Invalid feed UUID + { + "uuid": "4c92e4d0-ba1a-497c-83d8-b0c469d4e1be", + "action": "create", + "feed": { + "uuid": "not-a-uuid", + "feed_url": "https://example.com/feed5.rss/" + }, + "data": { + "subscribed_at": "2026-03-16T06:05:02.000Z" + } + } + ] +} +``` + +```jsonc title="Response" +{ + "data": [ + { + "uuid": "4790ba1b-1d4e-5f24-886e-7359eb98d52d", + "status": "created", + "received": "2026-03-16T06:05:02:000Z", + "feed": { + "uuid": "2fa174b5-2cd8-5c07-b086-fc60045fd9bf", + "feed_url": "https://example.com/feed1.rss/", + "created_at": "2026-03-16T06:05:02.000Z", + "updated_at": "2026-03-16T06:05:02.000Z" + }, + "subscription": { + "subscribed_at": "2026-03-16T05:20:48.000Z", + "created_at": "2026-03-16T06:05:02.000Z", + "updated_at": "2026-03-16T06:05:02.000Z" + } + }, + { + "uuid": "987f1cad-807f-4c00-88aa-277fd470697a", + "status": "updated", + "received": "2026-03-16T06:05:02:000Z", + "feed": { + "uuid": "34a12041-bdcd-5a3a-be5e-657315db7c44", + "feed_url": "https://example.com/feed2.rss/", + "created_at": "2026-03-15T03:05:01:000Z", + "updated_at": "2026-03-15T03:05:01:000Z" + }, + "subscription": { + "subscribed_at": "2026-03-15T03:05:01:000Z", + "created_at": "2026-03-15T03:05:01:000Z", + "updated_at": "2026-03-16T06:05:02:000Z" + } + }, + { + "uuid": "4dcf3a4a-42dd-4658-88f6-c71887a04bb8", + "status": "updated", + "received": "2026-03-16T06:05:02:000Z", + "feed": { + "uuid": "fc4ed290-4621-54fe-b5b4-a001343aeed7", + "feed_url": "https://example.com/feed3.rss/", + "created_at": "2026-03-15T03:05:01:000Z", + "updated_at": "2026-03-15T03:05:01:000Z" + }, + "subscription": { + "subscribed_at": "2026-03-15T03:05:01:000Z", + "unsubscribed_at": "2026-03-16T05:21:48.000Z", + "created_at": "2026-03-15T03:05:01:000Z", + "updated_at": "2026-03-16T06:05:02:000Z" + } + }, + { + "uuid": "100c7e48-085f-4906-a91e-40c3c4b1a73e", + "status": "invalid_action", + "received": "2026-03-16T06:05:02:000Z", + }, + { + "uuid": "100c7e48-085f-4906-a91e-40c3c4b1a73e", + "status": "malformed_feed_uuid", + "received": "2026-03-16T06:05:02:000Z", + } + ] +} +``` + +## 9. Synchronization + +### 9.1 Endpoint + +```http /[\\?\&](.*?)\=/ +GET /api/v1/subscriptions?cursor={cursor}&page_size=30&direction={direction}&include_errors=false +``` + +Clients use this endpoint to request actions that have been submitted to the server since the provided `cursor`. + +### 9.2 Purpose + +This endpoint returns a list of valid and applied actions taken on an authenticated principal's Subscriptions. Clients may use this endpoint to fetch a list of updates to Subscriptions since they last came online. + +### 9.3 Request parameters + +| Parameter | Type | In | Required | Description | +| ---------------- | ------ | ----- | -------- | ------------------------------------------------------------------------------------ | +| `cursor` | string | Query | No | The Base64-encoded cursor to query from | +| `page_size` | number | Query | No | The number of results to return per-page | +| `direction` | string | Query | No | The direction in which to search for results. `ascending` (default) or `descending`. | +| `include_errors` | boolean | Query | No | Whether to include invalid actions (default `false`) | + +### 9.4 Response format + +The Server MUST respond to valid requests with a `200` status. + +| Field | Type | Required | Description | +| --------------------------------------- | ---------------- | -------- | ----------------------------------------------------------------------- | +| `next_cursor` | string | No | The Base64-encoded cursor for the next page of results | +| `prev_cursor` | string | Yes | The Base64-encoded cursor for the current page of results | +| `has_next` | boolean | No | Whether there are more results for the given request | +| `data` | array | Yes | The array of response objects | +| `data.uuid` | UUID | Yes | The Client-generated UUIDv4 identifier for the action | +| `data.status` | string | Yes | The Server-authoritative [response status](#84-response-statuses) | +| `data.received` | string (RFC3339) | Yes | The Server-authoritative timestamp at which the request was received | +| `data[].feed` | object | No | The referenced Feed item for the action | +| `data[].feed.uuid` | UUID | Yes | The calculated UUIDv5 identifier for the feed | +| `data[].feed.feed_url` | string | Yes | The canonical URL of the feed RSS file | +| `data[].feed.created_at` | string (RFC3339) | Yes | The Server-authoritative creation timestamp for the Feed entity | +| `data[].feed.updated_at` | string (RFC3339) | No | The Server-authoritative last update timestamp for the Feed entity | +| `data[].subscription` | object | No | The Subscription entity | +| `data[].subscription.subscribed_at` | string (RFC3339) | No | The timestamp at which the User subscribed to the Feed | +| `data[].subscription.unsubscribed_at` | string (RFC3339) | No | The timestamp at which the User subscribed to the Feed | +| `data[].subscription.created_at` | string (RFC3339) | Yes | The Server-authoritative creation timestamp for the Subscription entity | +| `data[].subscription.updated_at` | string (RFC3339) | Yes | The Server-authoritative update timestamp for the Subscription entity | + +### 9.5 Client behavior + +1. The Client MAY provide any combination of supported query parameters, or none. +1. The Client SHOULD compare results in the response against its internal state to resolve the latest state of the User's Subscriptions. + +### 9.6 Server behavior + +1. The Server MUST discard invalid query parameters and use default parameters. +1. The Server MUST calculate and encode a cursor value for the given request using the provided parameters, or default parameters. +1. The Server MUST NOT return any actions that were not applied due to errors, unless the `include_errors` parameter is `true`. +1. The Server MAY use any method to calculate a cursor provided it meets the following criteria: + 1. The cursor MUST contain **at least one** ordered parameter. For example, `received` timestamp or incremental database IDs. + 1. The cursor MUST NOT contain any sensitive data. + 1. The cursor MUST be Base64-encoded. +1. The Server MUST return actions relating to the authenticated principal only. The Server MUST NOT return any actions associated with other users. +1. The Server SHOULD set sensible default values for any parameters whose default is not explicitly stated in this document. +1. The Server MUST return **at most** the number of results specified in the `page_size` parameter. + +### 9.7 Example + +```sh +curl -X GET "https://opa-server.test/api/v1/subscriptions?page_size=50" +``` + +```jsonc title="Response" +{ + "data": [ + { + "uuid": "4790ba1b-1d4e-5f24-886e-7359eb98d52d", + "status": "created", + "received": "2026-03-16T06:05:02:000Z", + "feed": { + "uuid": "2fa174b5-2cd8-5c07-b086-fc60045fd9bf", + "feed_url": "https://example.com/feed1.rss/", + "created_at": "2026-03-16T06:05:02.000Z", + "updated_at": "2026-03-16T06:05:02.000Z" + }, + "subscription": { + "subscribed_at": "2026-03-16T05:20:48.000Z", + "created_at": "2026-03-16T06:05:02.000Z", + "updated_at": "2026-03-16T06:05:02.000Z" + } + }, + { + "uuid": "987f1cad-807f-4c00-88aa-277fd470697a", + "status": "updated", + "received": "2026-03-16T06:05:02:000Z", + "feed": { + "uuid": "34a12041-bdcd-5a3a-be5e-657315db7c44", + "feed_url": "https://example.com/feed2.rss/", + "created_at": "2026-03-15T03:05:01:000Z", + "updated_at": "2026-03-15T03:05:01:000Z" + }, + "subscription": { + "subscribed_at": "2026-03-15T03:05:01:000Z", + "created_at": "2026-03-15T03:05:01:000Z", + "updated_at": "2026-03-16T06:05:02:000Z" + } + }, + { + "uuid": "4dcf3a4a-42dd-4658-88f6-c71887a04bb8", + "status": "updated", + "received": "2026-03-16T06:05:02:000Z", + "feed": { + "uuid": "fc4ed290-4621-54fe-b5b4-a001343aeed7", + "feed_url": "https://example.com/feed3.rss/", + "created_at": "2026-03-15T03:05:01:000Z", + "updated_at": "2026-03-15T03:05:01:000Z" + }, + "subscription": { + "subscribed_at": "2026-03-15T03:05:01:000Z", + "unsubscribed_at": "2026-03-16T05:21:48.000Z", + "created_at": "2026-03-15T03:05:01:000Z", + "updated_at": "2026-03-16T06:05:02:000Z" + } + }, + { + "uuid": "100c7e48-085f-4906-a91e-40c3c4b1a73e", + "status": "invalid_action", + "received": "2026-03-16T06:05:02:000Z", + }, + { + "uuid": "100c7e48-085f-4906-a91e-40c3c4b1a73e", + "status": "malformed_feed_uuid", + "received": "2026-03-16T06:05:02:000Z", + } + ], + "prev_cursor": "aWQ9MXxwYWdlX3NpemU9MzA=", + "has_next": false +} +``` + +[^1]: https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/tags/guid.md +[^2]: https://www.rfc-editor.org/rfc/rfc2119 +[^3]: https://www.rfc-editor.org/rfc/rfc3339 +[^4]: https://www.rfc-editor.org/rfc/rfc9562 diff --git a/src/content/docs/specs/subscriptions/add-new.mdx b/src/content/docs/specs/subscriptions/add-new.mdx deleted file mode 100644 index 422e8920..00000000 --- a/src/content/docs/specs/subscriptions/add-new.mdx +++ /dev/null @@ -1,243 +0,0 @@ ---- -title: Add a new subscription -description: Add a new subscription -sidebar: - order: 2 ---- - -import CoreAction from "@partials/_core-action.mdx"; - - - -```http title="Endpoint" -POST /v1/subscriptions -``` - -This endpoint enables clients to add new subscriptions to the system for the authenticated user. It returns an array of `success` responses for newly added subscriptions, and an array of `failure` responses for subscriptions that couldn't be added. - -| Field | Type | Required? | Description | -| ---------------------- | -------- | --------- | -------------------------------------------------------------------------------------------- | -| `feed_url` | String | Yes | The URL of the podcast RSS feed | -| `guid` | String | Yes | The globally unique ID of the podcast | -| `is_subscribed` | Boolean | Yes | Whether the user is subscribed to the podcast | -| `subscription_changed` | Datetime | Yes | The date on which the `is_subscribed` field was last updated. Presented in [ISO 8601 format] | - -## Request parameters - -The client MUST provide a list of objects containing the following parameters: - -| Field | Type | Required? | Description | -| ---------- | ------ | --------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `feed_url` | String | Yes | The URL of the podcast RSS feed. The client must provide a protocol (for example: `http` or `https`) and preserve any parameters | -| `guid` | String | No | The GUID found in the podcast RSS feed | - -:::caution[Important] -If a client passes a `guid` this MUST be treated as authoritative by the server. The client MAY pass a `guid` **only** if it is parsed from the podcast RSS feed. -::: - - - - - ```json - { - "subscriptions": [ - { - "feed_url": "https://example.com/rss1" - }, - { - "feed_url": "https://example.com/rss2" - }, - { - "feed_url": "https://example.com/rss3" - }, - { - "feed_url": "https://example.com/rss4", - "guid": "2d8bb39b-8d34-48d4-b223-a0d01eb27d71" - } - ] - } - ``` - - - - - ```xml - - - - https://example.com/feed1 - - - https://example.com/feed2 - - - https://example.com/feed3 - - - https://example.com/feed4 - 2d8bb39b-8d34-48d4-b223-a0d01eb27d71 - - - ``` - - - - -## Server-side behavior - -When new feeds are posted to the server, the server MUST return a success response to the client immediately to acknowledge the request. To ensure that data can be returned immediately, the following flow MUST be followed: - -1. The client sends a payload to the server -2. For each object in the payload, the server does the following: - 1. Checks if there's a `guid` entry in the payload - - If a `guid` is present, the server stores the `guid` for later use - - If no `guid` is present, the server generates a `guid` for later use - 2. Checks to see if there is an existing entry with the same `guid` or `feed_url` - - If an existing entry is found, the server sets the `is_subscribed` field to `true` and updates the `subscription_changed` date to the current date. If the `deleted` field is populated, the field is set to `NULL` to show that the subscription is active - - If no existing entry is found, the server creates a new subscription entry -3. The server returns a success payload containing the subscription information for each object in the request payload. - -![A flowchart diagram of the process](@assets/diagrams/subscriptions/add_new.png) - -### Subscription GUID update - -If the client doesn't send a `guid` in the subscription payload, the server MUST create one immediately to ensure the following: - -1. Each entry has an associated `guid` -2. The client receives a success response as quickly as possible - -Once this is done, the server SHOULD asynchronously verify that there isn't a more authoritative GUID available. The following flow should be used: - -1. The server fetches and parses the RSS feed to search for a [`guid` field in the `podcast` namespace](https://github.com/Podcastindex-org/podcast-namespace/blob/main/docs/1.0.md#guid). -2. If a more authoritative `guid` is found, the server must update the subscription entry as follows: - 1. Create a new subscription entry with the new `guid` - 2. Update the `new_guid` field in the existing entry to point to the new `guid` - 3. Update the `guid_changed` field in the existing entry to the current date - -![A diagram of the GUID update process](@assets/diagrams/subscriptions/guid_update.png) - -## Example request - - - - - ```console - $ curl --location '/subscriptions' \ - --header 'Content-Type: application/json' \ - --data '{ - "subscriptions": [ - { - "feed_url": "https://example.com/feed1" - }, - { - "feed_url": "https://example.com/feed2" - }, - { - "feed_url": "https://example.com/feed3" - }, - { - "feed_url": "example.com/feed4", - "guid": "2d8bb39b-8d34-48d4-b223-a0d01eb27d71" - } - ] - }' - ``` - - - - - ```console - $ curl --location '/subscriptions' \ - --header 'Content-Type: application/xml' \ - --data ' - - - https://example.com/feed1 - - - https://example.com/feed2 - - - https://example.com/feed3 - - - example.com/feed4 - 2d8bb39b-8d34-48d4-b223-a0d01eb27d71 - - ' - ``` - - - - -## Example 200 response - - - - - ```json - { - "success": [ - { - "feed_url": "https://example.com/rss1", - "guid": "8d1f8f09-4f50-4327-9a63-639bfb1cbd98", - "is_subscribed": true, - "subscription_changed": "2023-02-23T14:00:00.000Z" - }, - { - "feed_url": "https://example.com/rss2", - "guid": "968cb508-803c-493c-8ff2-9e397dadb83c", - "is_subscribed": true, - "subscription_changed": "2023-02-23T14:00:00.000Z" - }, - { - "feed_url": "https://example.com/rss3", - "guid": "e672c1f4-230d-4ab4-99d3-390a9f835ec1", - "is_subscribed": true, - "subscription_changed": "2023-02-23T14:00:00.000Z" - } - ], - "failure": [ - { - "feed_url": "example.com/rss4", - "message": "No protocol present" - } - ] - } - ``` - - - - - ```xml - - - - https://example.com/rss1 - 8d1f8f09-4f50-4327-9a63-639bfb1cbd98 - true - 2023-02-23T14:00:00.000Z - - - https://example.com/rss2 - 968cb508-803c-493c-8ff2-9e397dadb83c - true - 2023-02-23T14:00:00.000Z - - - https://example.com/rss3 - e672c1f4-230d-4ab4-99d3-390a9f835ec1 - true - 2023-02-23T14:00:00.000Z - - - example.com/rss4 - No protocol present - - - ``` - - - - -[ISO 8601 format]: https://www.iso.org/iso-8601-date-and-time-format.html diff --git a/src/content/docs/specs/subscriptions/delete.mdx b/src/content/docs/specs/subscriptions/delete.mdx deleted file mode 100644 index 4939f5d4..00000000 --- a/src/content/docs/specs/subscriptions/delete.mdx +++ /dev/null @@ -1,82 +0,0 @@ ---- -title: Delete a subscription -description: Fetch the status of a deletion process -sidebar: - order: 6 ---- - -import CoreAction from "@partials/_core-action.mdx"; - - - -```http title="Endpoint" -DELETE /v1/subscriptions/{guid} -``` - -This endpoint allows clients to mark a feed as deleted. This prevents the server from updating the feed in the background and prevents the server from returning any information, such as playback positions, related to the given associated feed. - -## Server-side behavior - -:::caution[Important] -The server MUST enact all cascade deletions using ACID transactions. If the deletion process fails at any point in the transaction, **all** transactions MUST be rolled back to maintain integrity. -::: - -To ensure that `DELETE` requests are handled asynchronously, the server MUST respond to deletion requests immediately with a `202 (Accepted)` status containing a `deletion_id`. This ID MUST correspond to a status object on the server containing details of the deletion process. The client MUST be able to [query the status of a deletion](/specs/subscriptions/status) to check its progress. - -| Parameter | Type | Required? | Description | -| ---------- | ------- | --------- | --------------------------------------------------------------------------------------------------- | -| `id` | Integer | Yes | The ID of the deletion object | -| `success` | Boolean | Yes | Whether or not the deletion was completed successfully | -| `complete` | Boolean | Yes | Whether or not the deletion process has finished | -| `message` | String | No | A status message indicating the current status of the deletion, or any errors that were encountered | - -The following flow MUST be followed: - -1. The client sends a `DELETE` request for a subscription object -2. The server creates a new deletion status object and returns the `deletion_id` in a `202 (Accepted)` response -3. The server attempts to perform a cascade delete on all related items - 1. If a failure occurs at any point in the process, all transactions are rolled back and the status object is updated to show the following: - - `complete`: Must be true - - `success`: Must be `false` - - `message`: Should be updated to contain a meaningful error message - 2. If all deletions are successful, the status object is updated to show the following: - - `complete`: Must be true - - `success`: Must be `true` - - `message`: Should be updated to contain a success message - -If the client attempts to [fetch a deleted subscription](/specs/subscriptions/get-all), the server MUST respond with a `410 (Gone)` status code to indicate the object and its associated data have been deleted. - -![A diagram of the deletion process](@assets/diagrams/subscriptions/delete_subscription.png) - -## Example request - -```console -$ curl --location --request DELETE \ -'/v1/subscriptions/2d8bb39b-8d34-48d4-b223-a0d01eb27d71' -``` - -## Example 202 response - - - - - ```json - { - "deletion_id": 25, - "message": "Deletion request was received and will be processed" - } - ``` - - - - - ```xml - - - 25 - Deletion request was received and will be processed - - ``` - - - diff --git a/src/content/docs/specs/subscriptions/get-all.mdx b/src/content/docs/specs/subscriptions/get-all.mdx deleted file mode 100644 index 69fc7957..00000000 --- a/src/content/docs/specs/subscriptions/get-all.mdx +++ /dev/null @@ -1,294 +0,0 @@ ---- -title: Get all subscriptions -description: Get all subscriptions for a user -sidebar: - order: 3 ---- - -import CoreAction from "@partials/_core-action.mdx"; - - - -```http title="Endpoint" -GET /v1/subscriptions -``` - -This endpoint enables clients to return all subscription information relating to the authenticated user. It returns pagination information and an array of `subscriptions`. - -## Response fields - -### Metadata - -| Field | Type | Required? | Description | -| ---------- | ------ | --------- | ------------------------------------------------ | -| `total` | Number | Yes | The total number of objects returned by the call | -| `page` | Number | Yes | The number of the page returned in the call | -| `per_page` | Number | Yes | The number of results returned per page | -| `next` | String | No | The URL for the next page of results | -| `previous` | String | No | The URL for the previous page of results | - -### Subscription fields - -| Field | Type | Required? | Description | -| ---------------------- | -------------- | --------- | ----------------------------------------------------------------------------------------------------- | -| `feed_url` | String | Yes | The URL of the podcast RSS feed | -| `guid` | String\ | Yes | The globally unique ID of the podcast | -| `is_subscribed` | Boolean | Yes | Whether the user is subscribed to the podcast | -| `subscription_changed` | Datetime | No | The date on which details relating to the subscription last changed. Presented in [ISO 8601 format] | -| `guid_changed` | Datetime | No | The date on which the podcast's `guid` or `new_guid` was last updated. Presented in [ISO 8601 format] | -| `new_guid` | String\ | No | The new GUID associated with the podcast | -| `deleted` | Datetime | No | The date on which the subscription was deleted. Only returned if the field is not `NULL` | - -## Parameters - -The client MAY add the following parameters to their call: - -| Field | Type | In | Required? | Description | -| ---------- | -------- | ----- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `since` | DateTime | Query | No | The date from which the server should return objects. The server only returns entries whose `subscription_changed`, `guid_changed`, or `deleted` fields are greater than this parameter. Expected in [ISO 8601 format] | -| `page` | Number | Query | No | The page of results to be returned by the server. Defaults to `1` if not present | -| `per_page` | Number | Query | No | The number of results to return in each call. Defaults to `50` if not present | - -:::note -If no `since` parameter is provided, the server MUST return all current subscription information. -::: - -## Server-side behavior - -If the entry contains a `new_guid`, the server MUST return the newest `guid` associated with the entry in the response's `new_guid` field. For example: if a subscription has received 2 new `guid`s, the server MUST return: - -- The subscription's `guid` as it was at the date passed in the `since` parameter, or the original entry's `guid` if no `since` parameter is passed -- The subscription's latest `guid` in the `new_guid` field - -This ensures the client has the most up-to-date entry for the subscription. - -![A flowchart demonstrating the GUID checking process](@assets/diagrams/subscriptions/check_guid.png) - -## Client behavior - -The client SHOULD update its local subscription data to match the information returned in the response. On receipt of a deleted subscription, the client SHOULD present the user with the option to remove their local data or [send their local data to the server](/specs/subscriptions/add-new) to reinstate the subscription details. - -### Resolution example - -This example demonstrates how the server resolves a `new_guid` field for a subscription that has received three GUIDs. Here is how the data is represented in the database: - -| `feed_url` | `guid` | `is_subscribed` | `subscription_changed` | `guid_change` | `new_guid` | -| -------------------------- | -------------------------------------- | --------------- | -------------------------- | -------------------------- | -------------------------------------- | -| `https://example.com/rss1` | `64c1593b-5a1e-4e89-b8a3-d91501065e80` | `true` | `2022-03-21T18:45:35.513Z` | `2022-03-21T19:00:00.000Z` | `daac3ce5-7b16-4cf0-8294-86ad71944a64` | -| `https://example.com/rss1` | `daac3ce5-7b16-4cf0-8294-86ad71944a64` | `true` | `2022-03-21T18:45:35.513Z` | `2022-12-23T10:24:14.670Z` | `36a47c4c-4aa3-428a-8132-3712a8422002` | -| `https://example.com/rss1` | `36a47c4c-4aa3-428a-8132-3712a8422002` | `true` | `2022-03-21T18:45:35.513Z` | `2022-12-23T10:24:14.670Z` | | - -#### Scenario 1 - -In this scenario, the client requests all subscriptions and **doesn't** pass a `since` parameter. This means the server passes the **original** GUID in the `guid` field, and the **latest** GUID in the `new_guid`field. - - - - - ```console - $ curl -X 'GET' \ - '/v1/subscriptions?page=1&per_page=5' \ - -H 'accept: application/json' - ``` - - - - - ```console - $ curl -X 'GET' \ - '/v1/subscriptions?page=1&per_page=5' \ - -H 'accept: application/xml' - ``` - - - - - - - - ```json {8, 11} collapse={2-4} - { - "total": 1, - "page": 1, - "per_page": 5, - "subscriptions": [ - { - "feed_url": "https://example.com/rss1", - "guid": "64c1593b-5a1e-4e89-b8a3-d91501065e80", - "is_subscribed": true, - "guid_changed": "2022-12-23T10:24:14.670Z", - "new_guid": "36a47c4c-4aa3-428a-8132-3712a8422002" - } - ] - } - ``` - - - - - ```xml {8, 11} collapse={3-5} - - - 1 - 1 - 5 - - https://example.com/rss1 - 64c1593b-5a1e-4e89-b8a3-d91501065e80 - true - 2022-12-23T10:24:14.670Z - 36a47c4c-4aa3-428a-8132-3712a8422002 - - - ``` - - - - -#### Scenario 2 - -In this scenario, the client requests all subscriptions and specifies a `since` date of `2022-05-30T00:00:00.000Z`. Since the first GUID change occurred before this date, and the second GUID change occurred after this date, the server responds with the **second** GUID in the `guid` field, and the **latest** GUID in the `new_guid` field. - - - - - ```console "since=2022-05-30T00%3A00%3A00.000Z" - $ curl -X 'GET' \ - '/v1/subscriptions?since=2022-05-30T00%3A00%3A00.000Z&page=1&per_page=5' \ - -H 'accept: application/json' - ``` - - - - - ```console "since=2022-05-30T00%3A00%3A00.000Z" - $ curl -X 'GET' \ - '/v1/subscriptions?since=2022-05-30T00%3A00%3A00.000Z&page=1&per_page=5' \ - -H 'accept: application/xml' - ``` - - - - - - - - ```json {8, 11} collapse={2-4} - { - "total": 1, - "page": 1, - "per_page": 5, - "subscriptions": [ - { - "feed_url": "https://example.com/rss1", - "guid": "daac3ce5-7b16-4cf0-8294-86ad71944a64", - "is_subscribed": true, - "guid_changed": "2022-12-23T10:24:14.670Z", - "new_guid": "36a47c4c-4aa3-428a-8132-3712a8422002" - } - ] - } - ``` - - - - - ```xml {8, 11} collapse={3-5} - - - 1 - 1 - 5 - - https://example.com/rss1 - daac3ce5-7b16-4cf0-8294-86ad71944a64 - true - 2022-12-23T10:24:14.670Z - 36a47c4c-4aa3-428a-8132-3712a8422002 - - - ``` - - - - -## Example request - - - - - ```console - $ curl -X 'GET' \ - '/v1/subscriptions?since=2022-04-23T18%3A25%3A34.511Z&page=1&per_page=5' \ - -H 'accept: application/json' - ``` - - - - - ```console - $ curl -X 'GET' \ - '/v1/subscriptions?since=2022-04-23T18%3A25%3A34.511Z&page=1&per_page=5' \ - -H 'accept: application/xml' - ``` - - - - -## Example 200 response - - - - - ```json - { - "total": 2, - "page": 1, - "per_page": 5, - "subscriptions": [ - { - "feed_url": "https://example.com/rss1", - "guid": "31740ac6-e39d-49cd-9179-634bcecf4143", - "is_subscribed": true, - "guid_changed": "2022-09-21T10:25:32.411Z", - "new_guid": "8d1f8f09-4f50-4327-9a63-639bfb1cbd98" - }, - { - "feed_url": "https://example.com/rss2", - "guid": "968cb508-803c-493c-8ff2-9e397dadb83c", - "is_subscribed": false, - "subscription_changed": "2022-04-24T17:53:21.573Z" - } - ] - } - ``` - - - - - ```xml - - - 2 - 1 - 5 - - https://example.com/rss1 - 31740ac6-e39d-49cd-9179-634bcecf4143 - true - 2022-09-21T10:25:32.411Z - 8d1f8f09-4f50-4327-9a63-639bfb1cbd98 - - - https://example.com/rss2 - 968cb508-803c-493c-8ff2-9e397dadb83c - false - 2022-04-24T17:53:21.573Z - - - ``` - - - - -[ISO 8601 format]: https://www.iso.org/iso-8601-date-and-time-format.html diff --git a/src/content/docs/specs/subscriptions/get-single.mdx b/src/content/docs/specs/subscriptions/get-single.mdx deleted file mode 100644 index 601623ee..00000000 --- a/src/content/docs/specs/subscriptions/get-single.mdx +++ /dev/null @@ -1,126 +0,0 @@ ---- -title: Get a single subscription -description: Get a single subscription for a user -sidebar: - order: 4 ---- - -import CoreAction from "@partials/_core-action.mdx"; - - - -```http title="Endpoint" -GET /v1/subscriptions/{guid} -``` - -This endpoint returns subscription information relating to a specific subscription for the authenticated user. It returns the following information: - -| Field | Type | Required? | Description | -| ---------------------- | -------------- | --------- | ----------------------------------------------------------------------------------------------------- | -| `feed_url` | String | Yes | The URL of the podcast RSS feed | -| `guid` | String\ | Yes | The globally unique ID of the podcast | -| `is_subscribed` | Boolean | Yes | Whether the user is subscribed to the podcast or not | -| `subscription_changed` | Datetime | No | The date on which the `is_subscribed` field was last updated. Presented in [ISO 8601 format] | -| `guid_changed` | Datetime | No | The date on which the podcast's `guid` or `new_guid` was last updated. Presented in [ISO 8601 format] | -| `new_guid` | String\ | No | The new GUID associated with the podcast | -| `deleted` | Datetime | No | The date on which the subscription was deleted. Only returned if the field is not `NULL` | - -## Parameters - -The client MUST send the subscription's `guid` in the path of the request. - -## Server-side behavior - -If the entry contains a `new_guid`, the server MUST return the newest `guid` associated with the entry in the response's `new_guid` field. For example: if a subscription has received 2 new `guid`s, the server MUST return: - -- The subscription's `guid` passed in the request path -- The subscription's latest `guid` in the `new_guid` field - -This ensures the client has the most up-to-date entry for the subscription. - -![A flowchart demonstrating the GUID checking process](@assets/diagrams/subscriptions/check_guid.png) - -## Client behavior - -The client SHOULD update its local subscription data to match the information returned in the response. On receipt of a deleted subscription, the client SHOULD present the user with the option to remove their local data or [send their local data to the server](/specs/subscriptions/add-new) to reinstate the subscription details. - -## Example request - - - - - ```console - $ curl -X 'GET' \ - '/v1/subscriptions/968cb508-803c-493c-8ff2-9e397dadb83c' \ - -H 'accept: application/json' - ``` - - - - - ```console - $ curl -X 'GET' \ - '/v1/subscriptions/968cb508-803c-493c-8ff2-9e397dadb83c' \ - -H 'accept: application/xml' - ``` - - - - -## Example 200 response - - - - - ```json - { - "feed_url": "https://example.com/feed2", - "guid": "968cb508-803c-493c-8ff2-9e397dadb83c", - "is_subscribed": true - } - ``` - - - - - ```xml - - - https://example.com/feed2 - 968cb508-803c-493c-8ff2-9e397dadb83c - true - - ``` - - - - -## Example 410 response - -If a subscription has been [deleted](/specs/subscriptions/delete), the server must respond with a `410 (Gone)` response to inform the client. - - - - - ```json - { - "code": 410, - "message": "Subscription has been deleted" - } - ``` - - - - - ```xml - - - 410 - Subscription has been deleted - - ``` - - - - -[ISO 8601 format]: https://www.iso.org/iso-8601-date-and-time-format.html diff --git a/src/content/docs/specs/subscriptions/index.mdx b/src/content/docs/specs/subscriptions/index.mdx deleted file mode 100644 index a6975902..00000000 --- a/src/content/docs/specs/subscriptions/index.mdx +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: Subscriptions endpoint -description: An endpoint for syncing subscriptions between devices. -prev: false -sidebar: - label: Overview - order: 1 ---- - -import CoreEndpoint from "@partials/_core-endpoint.mdx"; - - - -The subscriptions endpoint is used to synchronize subscriptions between a server and connected clients. The server is treated as the authoritative source for subscription information. Clients can query the endpoint by specifying the datetime from which they want to fetch changes to ensure they only fetch information that is relevant to them since their last sync. - -Subscriptions represent the feeds a user has subscribed to. A subscription object stores essential information about each subscription and acts as an index that links other activity information together. - -## Important data fields - -| Field | Type | Nullable? | Description | -| ---------------------- | -------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `feed_url` | String | No | The URL of the podcast RSS feed | -| `guid` | String\ | No | The globally unique ID of the podcast | -| `is_subscribed` | Boolean | No | Whether the user is subscribed to the podcast | -| `subscription_changed` | Datetime | No | The date on which the `is_subscribed` field was last updated. Presented in [ISO 8601 format] | -| `guid_changed` | Datetime | No | The date on which the podcast's `guid` or `new_guid` was last updated. Presented in [ISO 8601 format] | -| `new_guid` | String\ | Yes | The new GUID associated with the podcast | -| `deleted` | Datetime | Yes | The date on which data associated with the subscription was deleted by the user. This field is used to determine whether a `410 (Gone)` response should be returned | - -:::note[Tombstoning] -Servers SHOULD hold all previous `guid` and `feed_url` field data with a link to the succeeding data (such that a path of values can be followed) or with a link to the most recent data. This enables the server to handle situations in which clients submit old data. For example: - -- A user finds a podcast, whose URL had changed, and adds the old URL in the app. Because the client doesn't have the old URL in its database, it recognizes the podcast as **new** and POSTs the `feed_url` to the `/subscriptions` endpoint. If the user is already subscribed to the podcast (with the current feed URL) this would lead to a duplicate subscription. -- A user has a device that they didn't use for a very long time. In that time, a podcaster added a GUID in their feed, leading to updated data in this field. When the client connects to the server again to pull all episode changes since the last connection, it retrieves episodes with their current subscription `guid`. The client won't recognize the subscription and fail to update the status of episodes. - ::: - -[ISO 8601 format]: https://www.iso.org/iso-8601-date-and-time-format.html diff --git a/src/content/docs/specs/subscriptions/status.mdx b/src/content/docs/specs/subscriptions/status.mdx deleted file mode 100644 index 91b59944..00000000 --- a/src/content/docs/specs/subscriptions/status.mdx +++ /dev/null @@ -1,137 +0,0 @@ ---- -title: Deletion status endpoint -description: Fetch the status of a deletion process -sidebar: - order: 7 ---- - -import CoreAction from "@partials/_core-action.mdx"; - - - -```http title="Endpoint" -GET /v1/deletions/{id} -``` - -This endpoint enables clients to query the status of a [deletion](/specs/subscriptions/delete). When a client sends a `DELETE` request, the server MUST respond with a `deletion_id` that can be used with this endpoint to check whether a deletion has been successfully actioned. - -| Parameter | Type | Required? | Description | -| ------------- | ------- | --------- | --------------------------------------------------------------------------------------------------- | --------- | --------- | -| `deletion_id` | Integer | Yes | The ID of the deletion object | -| `status` | String | Yes | A status message indicating the status of the deletion. Available values: `SUCCESS` | `FAILURE` | `PENDING` | -| `message` | String | No | A status message indicating the current status of the deletion, or any errors that were encountered | - -## Parameters - -The client MUST send the deletion's `id` in the path of the request. - -## Example request - - - - - ```console - $ curl -X 'GET' \ - '/v1/deletions/25' \ - -H 'accept: application/json' - ``` - - - - - ```console - $ curl -X 'GET' \ - '/v1/deletions/25' \ - -H 'accept: application/xml' - ``` - - - - -## Example 200 response - -The server MUST send a `200 (Success)` if it can fetch a status object without issue. This response MUST contain information about the `deletion_id` passed in the query path. - -### Successful deletion - - - - - ```json {3} - { - "deletion_id": 25, - "status": "SUCCESS", - "message": "Subscription deleted successfully" - } - ``` - - - - - ```xml {3} - - - 25 - SUCCESS - Subscription deleted successfully - - ``` - - - - -### Pending deletion - - - - - ```json {3} - { - "deletion_id": 25, - "status": "PENDING", - "message": "Deletion is pending" - } - ``` - - - - - ```xml {3} - - - 25 - PENDING - Deletion is pending - - ``` - - - - -### Failed deletion - - - - - ```json {3} - { - "deletion_id": 25, - "status": "FAILURE", - "message": "The deletion process encountered an error and was rolled back" - } - ``` - - - - - ```xml {3} - - - 25 - FAILURE - The deletion process encountered an error and was rolled back - - ``` - - - diff --git a/src/content/docs/specs/subscriptions/update.mdx b/src/content/docs/specs/subscriptions/update.mdx deleted file mode 100644 index d7bb440a..00000000 --- a/src/content/docs/specs/subscriptions/update.mdx +++ /dev/null @@ -1,127 +0,0 @@ ---- -title: Update a subscription -description: Update details about a subscription -sidebar: - order: 5 ---- - -import CoreAction from "@partials/_core-action.mdx"; - - - -```http title="Endpoint" -PATCH /v1/subscriptions/{guid} -``` - -This endpoint allows clients to update information about a subscription. The client MAY update the following information: - -- The podcast's GUID -- The podcast's feed URL -- An update to the subscription status for the user - -This endpoint returns the following information: - -| Field | Type | Required? | Description | -| ---------------------- | -------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `new_feed_url` | String | No | The URL of the podcast RSS feed. Only returned if the `feed_url` field was updated by the request | -| `is_subscribed` | Boolean | No | Whether the user is subscribed to the podcast or not. Only returned if the `is_subscribed` field was updated by the request | -| `subscription_changed` | Datetime | No | The date on which the `is_subscribed`or `feed_url` field was last updated. Presented in [ISO 8601 format]. Only returned if the `is_subscribed` field was updated by the request | -| `guid_changed` | Datetime | No | The date on which the podcast's GUID was last updated. Presented in [ISO 8601 format]. Only returned if the `guid` field was updated by the request | -| `new_guid` | String\ | No | The new GUID associated with the podcast. Only returned if the `guid` field was updated by the request | - -## Parameters - -The client MUST pass the subscription GUID in the query path and add at least one field update in the request body. - -| Parameter | Type | In | Required? | Description | -| --------------- | --------------- | ----- | --------- | ------------------------------------------------------------ | -| `guid` | String | Query | Yes | The GUID of the subscription object that needs to be updated | -| `new_feed_url` | String | Body | No | The URL of the new RSS feed for the subscription | -| `new_guid` | String \ | Body | No | The new GUID of the podcast | -| `is_subscribed` | Boolean | Body | No | Whether the user is subscribed to the podcast or not | - -## Server-side behavior - -On receipt of a PATCH request for a subscription, the server MUST do the following: - -1. If the subscription in the request has a `new_guid` specified in the database, follow the `new_guid` chain to find the **latest** version of the subscription -2. If the request contains a `new_feed_url` parameter: - 1. Update the subscription entry's `feed_url` field to the new value - 2. Update the subscription entry's `subscription_changed` field to the current date -3. If the request contains a `new_guid` parameter: - 1. Check if the GUID is already present in the system - 2. If the GUID is already present, update the subscription entry's `new_guid` field to point to the existing entry - 3. If the GUID isn't already present, create a new subscription entry and update the existing entry's `new_guid` field to point to the newly created entry - 4. Update the subscription entry's `guid_changed` to the current date -4. If the request contains an `is_subscribed` parameter: - 1. Update the subscription entry's `is_subscribed` to the new value - 2. Update the subscription entry's `subscription_changed` field to the current date -5. Return a summary of the changes - -![A flowchart of the subscription update process](@assets/diagrams/subscriptions/update_subscription.png) - -## Example request - - - - - ```console - $ curl --location --request PATCH '/subscriptions/2d8bb39b-8d34-48d4-b223-a0d01eb27d71' \ - --header 'Content-Type: application/json' \ - --data '{ - "new_feed_url": "https://example.com/rss5", - "new_guid": "965fcecf-ce04-482b-b57c-3119b866cc61", - "is_subscribed": false - }' - ``` - - - - - ```console - $ curl --location --request PATCH '/subscriptions/2d8bb39b-8d34-48d4-b223-a0d01eb27d71' \ - --header 'Content-Type: application/xml' \ - --data ' - - https://example.com/rss5 - 965fcecf-ce04-482b-b57c-3119b866cc61 - false - ' - ``` - - - - -## Example 200 response - - - - - ```json - { - "new_feed_url": "https://example.com/rss5", - "is_subscribed": false, - "subscription_changed": "2023-02-23T14:41:00.000Z", - "guid_changed": "2023-02-23T14:41:00.000Z", - "new_guid": "965fcecf-ce04-482b-b57c-3119b866cc61" - } - ``` - - - - - ```xml - - - https://example.com/rss5 - false - 2023-02-23T14:41:00.000Z - 2023-02-23T14:41:00.000Z - 965fcecf-ce04-482b-b57c-3119b866cc61 - - ``` - - - - -[ISO 8601 format]: https://www.iso.org/iso-8601-date-and-time-format.html From ef160ed88883173168034dafc1fa967bf9e9f1ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20Ainsworth?= Date: Mon, 16 Mar 2026 06:48:44 +0900 Subject: [PATCH 2/7] Add missing files --- .vale.ini | 7 + astro.config.mjs | 24 +- src/content/docs/partials/_core-action.mdx | 3 - src/content/docs/partials/_core-endpoint.mdx | 3 - styles/.vale-config/2-MDX.ini | 5 + styles/Google/AMPM.yml | 9 + styles/Google/Acronyms.yml | 64 ++ styles/Google/Colons.yml | 8 + styles/Google/Contractions.yml | 30 + styles/Google/DateFormat.yml | 9 + styles/Google/Ellipses.yml | 9 + styles/Google/EmDash.yml | 13 + styles/Google/Exclamation.yml | 12 + styles/Google/FirstPerson.yml | 13 + styles/Google/Gender.yml | 9 + styles/Google/GenderBias.yml | 43 ++ styles/Google/HeadingPunctuation.yml | 13 + styles/Google/Headings.yml | 29 + styles/Google/Latin.yml | 11 + styles/Google/LyHyphens.yml | 14 + styles/Google/OptionalPlurals.yml | 12 + styles/Google/Ordinal.yml | 7 + styles/Google/OxfordComma.yml | 7 + styles/Google/Parens.yml | 7 + styles/Google/Passive.yml | 184 +++++ styles/Google/Periods.yml | 7 + styles/Google/Quotes.yml | 7 + styles/Google/Ranges.yml | 7 + styles/Google/Semicolons.yml | 8 + styles/Google/Slang.yml | 11 + styles/Google/Spacing.yml | 10 + styles/Google/Spelling.yml | 10 + styles/Google/Units.yml | 8 + styles/Google/We.yml | 11 + styles/Google/Will.yml | 7 + styles/Google/WordList.yml | 80 +++ styles/Google/meta.json | 4 + styles/Google/vocab.txt | 0 styles/write-good/Cliches.yml | 702 +++++++++++++++++++ styles/write-good/E-Prime.yml | 32 + styles/write-good/Illusions.yml | 11 + styles/write-good/Passive.yml | 183 +++++ styles/write-good/README.md | 27 + styles/write-good/So.yml | 5 + styles/write-good/ThereIs.yml | 6 + styles/write-good/TooWordy.yml | 221 ++++++ styles/write-good/Weasel.yml | 29 + styles/write-good/meta.json | 4 + 48 files changed, 1912 insertions(+), 23 deletions(-) create mode 100644 .vale.ini delete mode 100644 src/content/docs/partials/_core-action.mdx delete mode 100644 src/content/docs/partials/_core-endpoint.mdx create mode 100644 styles/.vale-config/2-MDX.ini create mode 100644 styles/Google/AMPM.yml create mode 100644 styles/Google/Acronyms.yml create mode 100644 styles/Google/Colons.yml create mode 100644 styles/Google/Contractions.yml create mode 100644 styles/Google/DateFormat.yml create mode 100644 styles/Google/Ellipses.yml create mode 100644 styles/Google/EmDash.yml create mode 100644 styles/Google/Exclamation.yml create mode 100644 styles/Google/FirstPerson.yml create mode 100644 styles/Google/Gender.yml create mode 100644 styles/Google/GenderBias.yml create mode 100644 styles/Google/HeadingPunctuation.yml create mode 100644 styles/Google/Headings.yml create mode 100644 styles/Google/Latin.yml create mode 100644 styles/Google/LyHyphens.yml create mode 100644 styles/Google/OptionalPlurals.yml create mode 100644 styles/Google/Ordinal.yml create mode 100644 styles/Google/OxfordComma.yml create mode 100644 styles/Google/Parens.yml create mode 100644 styles/Google/Passive.yml create mode 100644 styles/Google/Periods.yml create mode 100644 styles/Google/Quotes.yml create mode 100644 styles/Google/Ranges.yml create mode 100644 styles/Google/Semicolons.yml create mode 100644 styles/Google/Slang.yml create mode 100644 styles/Google/Spacing.yml create mode 100644 styles/Google/Spelling.yml create mode 100644 styles/Google/Units.yml create mode 100644 styles/Google/We.yml create mode 100644 styles/Google/Will.yml create mode 100644 styles/Google/WordList.yml create mode 100644 styles/Google/meta.json create mode 100644 styles/Google/vocab.txt create mode 100644 styles/write-good/Cliches.yml create mode 100644 styles/write-good/E-Prime.yml create mode 100644 styles/write-good/Illusions.yml create mode 100644 styles/write-good/Passive.yml create mode 100644 styles/write-good/README.md create mode 100644 styles/write-good/So.yml create mode 100644 styles/write-good/ThereIs.yml create mode 100644 styles/write-good/TooWordy.yml create mode 100644 styles/write-good/Weasel.yml create mode 100644 styles/write-good/meta.json diff --git a/.vale.ini b/.vale.ini new file mode 100644 index 00000000..7f70c7fc --- /dev/null +++ b/.vale.ini @@ -0,0 +1,7 @@ +StylesPath = styles +MinAlertLevel = error + +Packages = Google, write-good, MDX + +[*.{md,mdx}] +BasedOnStyles = Vale, Google, write-good diff --git a/astro.config.mjs b/astro.config.mjs index d82197eb..fe1abeaa 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -5,7 +5,7 @@ import { remarkDefinitionList, defListHastHandlers, } from "remark-definition-list"; -import starlightOpenAPI, { openAPISidebarGroups } from 'starlight-openapi' +import starlightOpenAPI, { openAPISidebarGroups } from "starlight-openapi"; // https://astro.build/config export default defineConfig({ @@ -26,8 +26,8 @@ export default defineConfig({ base: "/explorer", label: "API explorer", schema: "./schema.yml", - } - ]) + }, + ]), ], sidebar: [ { @@ -40,19 +40,9 @@ export default defineConfig({ }, { label: "Specifications", - items: [ - { - label: "Introduction", - link: "specs", - }, - { - label: "Subscriptions", - collapsed: true, - autogenerate: { - directory: "specs/subscriptions", - }, - }, - ], + autogenerate: { + directory: "specs", + }, }, ...openAPISidebarGroups, ], @@ -68,7 +58,7 @@ export default defineConfig({ "TabItem", ], }, - "src/components/SponsorCallout.astro" + "src/components/SponsorCallout.astro", ], }), ], diff --git a/src/content/docs/partials/_core-action.mdx b/src/content/docs/partials/_core-action.mdx deleted file mode 100644 index cc3c862b..00000000 --- a/src/content/docs/partials/_core-action.mdx +++ /dev/null @@ -1,3 +0,0 @@ -:::caution[Core action] -This is a **core action**. All server implementations MUST support it. -::: diff --git a/src/content/docs/partials/_core-endpoint.mdx b/src/content/docs/partials/_core-endpoint.mdx deleted file mode 100644 index d832eefb..00000000 --- a/src/content/docs/partials/_core-endpoint.mdx +++ /dev/null @@ -1,3 +0,0 @@ -:::caution[Core endpoint] -This is a **core endpoint**. All server implementations MUST support it. -::: diff --git a/styles/.vale-config/2-MDX.ini b/styles/.vale-config/2-MDX.ini new file mode 100644 index 00000000..02ea72b3 --- /dev/null +++ b/styles/.vale-config/2-MDX.ini @@ -0,0 +1,5 @@ +[*.mdx] +# Exclude: +# +# - Non-JS inline expressions (which cause Acorn to throw) +TokenIgnores = '({#[^\n}]+})(?!`)' diff --git a/styles/Google/AMPM.yml b/styles/Google/AMPM.yml new file mode 100644 index 00000000..37b49edf --- /dev/null +++ b/styles/Google/AMPM.yml @@ -0,0 +1,9 @@ +extends: existence +message: "Use 'AM' or 'PM' (preceded by a space)." +link: "https://developers.google.com/style/word-list" +level: error +nonword: true +tokens: + - '\d{1,2}[AP]M\b' + - '\d{1,2} ?[ap]m\b' + - '\d{1,2} ?[aApP]\.[mM]\.' diff --git a/styles/Google/Acronyms.yml b/styles/Google/Acronyms.yml new file mode 100644 index 00000000..f41af018 --- /dev/null +++ b/styles/Google/Acronyms.yml @@ -0,0 +1,64 @@ +extends: conditional +message: "Spell out '%s', if it's unfamiliar to the audience." +link: 'https://developers.google.com/style/abbreviations' +level: suggestion +ignorecase: false +# Ensures that the existence of 'first' implies the existence of 'second'. +first: '\b([A-Z]{3,5})\b' +second: '(?:\b[A-Z][a-z]+ )+\(([A-Z]{3,5})\)' +# ... with the exception of these: +exceptions: + - API + - ASP + - CLI + - CPU + - CSS + - CSV + - DEBUG + - DOM + - DPI + - FAQ + - GCC + - GDB + - GET + - GPU + - GTK + - GUI + - HTML + - HTTP + - HTTPS + - IDE + - JAR + - JSON + - JSX + - LESS + - LLDB + - NET + - NOTE + - NVDA + - OSS + - PATH + - PDF + - PHP + - POST + - RAM + - REPL + - RSA + - SCM + - SCSS + - SDK + - SQL + - SSH + - SSL + - SVG + - TBD + - TCP + - TODO + - URI + - URL + - USB + - UTF + - XML + - XSS + - YAML + - ZIP diff --git a/styles/Google/Colons.yml b/styles/Google/Colons.yml new file mode 100644 index 00000000..4a027c30 --- /dev/null +++ b/styles/Google/Colons.yml @@ -0,0 +1,8 @@ +extends: existence +message: "'%s' should be in lowercase." +link: 'https://developers.google.com/style/colons' +nonword: true +level: warning +scope: sentence +tokens: + - '(?=1.0.0" +} diff --git a/styles/Google/vocab.txt b/styles/Google/vocab.txt new file mode 100644 index 00000000..e69de29b diff --git a/styles/write-good/Cliches.yml b/styles/write-good/Cliches.yml new file mode 100644 index 00000000..c9531438 --- /dev/null +++ b/styles/write-good/Cliches.yml @@ -0,0 +1,702 @@ +extends: existence +message: "Try to avoid using clichés like '%s'." +ignorecase: true +level: warning +tokens: + - a chip off the old block + - a clean slate + - a dark and stormy night + - a far cry + - a fine kettle of fish + - a loose cannon + - a penny saved is a penny earned + - a tough row to hoe + - a word to the wise + - ace in the hole + - acid test + - add insult to injury + - against all odds + - air your dirty laundry + - all fun and games + - all in a day's work + - all talk, no action + - all thumbs + - all your eggs in one basket + - all's fair in love and war + - all's well that ends well + - almighty dollar + - American as apple pie + - an axe to grind + - another day, another dollar + - armed to the teeth + - as luck would have it + - as old as time + - as the crow flies + - at loose ends + - at my wits end + - avoid like the plague + - babe in the woods + - back against the wall + - back in the saddle + - back to square one + - back to the drawing board + - bad to the bone + - badge of honor + - bald faced liar + - ballpark figure + - banging your head against a brick wall + - baptism by fire + - barking up the wrong tree + - bat out of hell + - be all and end all + - beat a dead horse + - beat around the bush + - been there, done that + - beggars can't be choosers + - behind the eight ball + - bend over backwards + - benefit of the doubt + - bent out of shape + - best thing since sliced bread + - bet your bottom dollar + - better half + - better late than never + - better mousetrap + - better safe than sorry + - between a rock and a hard place + - beyond the pale + - bide your time + - big as life + - big cheese + - big fish in a small pond + - big man on campus + - bigger they are the harder they fall + - bird in the hand + - bird's eye view + - birds and the bees + - birds of a feather flock together + - bit the hand that feeds you + - bite the bullet + - bite the dust + - bitten off more than he can chew + - black as coal + - black as pitch + - black as the ace of spades + - blast from the past + - bleeding heart + - blessing in disguise + - blind ambition + - blind as a bat + - blind leading the blind + - blood is thicker than water + - blood sweat and tears + - blow off steam + - blow your own horn + - blushing bride + - boils down to + - bolt from the blue + - bone to pick + - bored stiff + - bored to tears + - bottomless pit + - boys will be boys + - bright and early + - brings home the bacon + - broad across the beam + - broken record + - brought back to reality + - bull by the horns + - bull in a china shop + - burn the midnight oil + - burning question + - burning the candle at both ends + - burst your bubble + - bury the hatchet + - busy as a bee + - by hook or by crook + - call a spade a spade + - called onto the carpet + - calm before the storm + - can of worms + - can't cut the mustard + - can't hold a candle to + - case of mistaken identity + - cat got your tongue + - cat's meow + - caught in the crossfire + - caught red-handed + - checkered past + - chomping at the bit + - cleanliness is next to godliness + - clear as a bell + - clear as mud + - close to the vest + - cock and bull story + - cold shoulder + - come hell or high water + - cool as a cucumber + - cool, calm, and collected + - cost a king's ransom + - count your blessings + - crack of dawn + - crash course + - creature comforts + - cross that bridge when you come to it + - crushing blow + - cry like a baby + - cry me a river + - cry over spilt milk + - crystal clear + - curiosity killed the cat + - cut and dried + - cut through the red tape + - cut to the chase + - cute as a bugs ear + - cute as a button + - cute as a puppy + - cuts to the quick + - dark before the dawn + - day in, day out + - dead as a doornail + - devil is in the details + - dime a dozen + - divide and conquer + - dog and pony show + - dog days + - dog eat dog + - dog tired + - don't burn your bridges + - don't count your chickens + - don't look a gift horse in the mouth + - don't rock the boat + - don't step on anyone's toes + - don't take any wooden nickels + - down and out + - down at the heels + - down in the dumps + - down the hatch + - down to earth + - draw the line + - dressed to kill + - dressed to the nines + - drives me up the wall + - dull as dishwater + - dyed in the wool + - eagle eye + - ear to the ground + - early bird catches the worm + - easier said than done + - easy as pie + - eat your heart out + - eat your words + - eleventh hour + - even the playing field + - every dog has its day + - every fiber of my being + - everything but the kitchen sink + - eye for an eye + - face the music + - facts of life + - fair weather friend + - fall by the wayside + - fan the flames + - feast or famine + - feather your nest + - feathered friends + - few and far between + - fifteen minutes of fame + - filthy vermin + - fine kettle of fish + - fish out of water + - fishing for a compliment + - fit as a fiddle + - fit the bill + - fit to be tied + - flash in the pan + - flat as a pancake + - flip your lid + - flog a dead horse + - fly by night + - fly the coop + - follow your heart + - for all intents and purposes + - for the birds + - for what it's worth + - force of nature + - force to be reckoned with + - forgive and forget + - fox in the henhouse + - free and easy + - free as a bird + - fresh as a daisy + - full steam ahead + - fun in the sun + - garbage in, garbage out + - gentle as a lamb + - get a kick out of + - get a leg up + - get down and dirty + - get the lead out + - get to the bottom of + - get your feet wet + - gets my goat + - gilding the lily + - give and take + - go against the grain + - go at it tooth and nail + - go for broke + - go him one better + - go the extra mile + - go with the flow + - goes without saying + - good as gold + - good deed for the day + - good things come to those who wait + - good time was had by all + - good times were had by all + - greased lightning + - greek to me + - green thumb + - green-eyed monster + - grist for the mill + - growing like a weed + - hair of the dog + - hand to mouth + - happy as a clam + - happy as a lark + - hasn't a clue + - have a nice day + - have high hopes + - have the last laugh + - haven't got a row to hoe + - head honcho + - head over heels + - hear a pin drop + - heard it through the grapevine + - heart's content + - heavy as lead + - hem and haw + - high and dry + - high and mighty + - high as a kite + - hit paydirt + - hold your head up high + - hold your horses + - hold your own + - hold your tongue + - honest as the day is long + - horns of a dilemma + - horse of a different color + - hot under the collar + - hour of need + - I beg to differ + - icing on the cake + - if the shoe fits + - if the shoe were on the other foot + - in a jam + - in a jiffy + - in a nutshell + - in a pig's eye + - in a pinch + - in a word + - in hot water + - in the gutter + - in the nick of time + - in the thick of it + - in your dreams + - it ain't over till the fat lady sings + - it goes without saying + - it takes all kinds + - it takes one to know one + - it's a small world + - it's only a matter of time + - ivory tower + - Jack of all trades + - jockey for position + - jog your memory + - joined at the hip + - judge a book by its cover + - jump down your throat + - jump in with both feet + - jump on the bandwagon + - jump the gun + - jump to conclusions + - just a hop, skip, and a jump + - just the ticket + - justice is blind + - keep a stiff upper lip + - keep an eye on + - keep it simple, stupid + - keep the home fires burning + - keep up with the Joneses + - keep your chin up + - keep your fingers crossed + - kick the bucket + - kick up your heels + - kick your feet up + - kid in a candy store + - kill two birds with one stone + - kiss of death + - knock it out of the park + - knock on wood + - knock your socks off + - know him from Adam + - know the ropes + - know the score + - knuckle down + - knuckle sandwich + - knuckle under + - labor of love + - ladder of success + - land on your feet + - lap of luxury + - last but not least + - last hurrah + - last-ditch effort + - law of the jungle + - law of the land + - lay down the law + - leaps and bounds + - let sleeping dogs lie + - let the cat out of the bag + - let the good times roll + - let your hair down + - let's talk turkey + - letter perfect + - lick your wounds + - lies like a rug + - life's a bitch + - life's a grind + - light at the end of the tunnel + - lighter than a feather + - lighter than air + - like clockwork + - like father like son + - like taking candy from a baby + - like there's no tomorrow + - lion's share + - live and learn + - live and let live + - long and short of it + - long lost love + - look before you leap + - look down your nose + - look what the cat dragged in + - looking a gift horse in the mouth + - looks like death warmed over + - loose cannon + - lose your head + - lose your temper + - loud as a horn + - lounge lizard + - loved and lost + - low man on the totem pole + - luck of the draw + - luck of the Irish + - make hay while the sun shines + - make money hand over fist + - make my day + - make the best of a bad situation + - make the best of it + - make your blood boil + - man of few words + - man's best friend + - mark my words + - meaningful dialogue + - missed the boat on that one + - moment in the sun + - moment of glory + - moment of truth + - money to burn + - more power to you + - more than one way to skin a cat + - movers and shakers + - moving experience + - naked as a jaybird + - naked truth + - neat as a pin + - needle in a haystack + - needless to say + - neither here nor there + - never look back + - never say never + - nip and tuck + - nip it in the bud + - no guts, no glory + - no love lost + - no pain, no gain + - no skin off my back + - no stone unturned + - no time like the present + - no use crying over spilled milk + - nose to the grindstone + - not a hope in hell + - not a minute's peace + - not in my backyard + - not playing with a full deck + - not the end of the world + - not written in stone + - nothing to sneeze at + - nothing ventured nothing gained + - now we're cooking + - off the top of my head + - off the wagon + - off the wall + - old hat + - older and wiser + - older than dirt + - older than Methuselah + - on a roll + - on cloud nine + - on pins and needles + - on the bandwagon + - on the money + - on the nose + - on the rocks + - on the spot + - on the tip of my tongue + - on the wagon + - on thin ice + - once bitten, twice shy + - one bad apple doesn't spoil the bushel + - one born every minute + - one brick short + - one foot in the grave + - one in a million + - one red cent + - only game in town + - open a can of worms + - open and shut case + - open the flood gates + - opportunity doesn't knock twice + - out of pocket + - out of sight, out of mind + - out of the frying pan into the fire + - out of the woods + - out on a limb + - over a barrel + - over the hump + - pain and suffering + - pain in the + - panic button + - par for the course + - part and parcel + - party pooper + - pass the buck + - patience is a virtue + - pay through the nose + - penny pincher + - perfect storm + - pig in a poke + - pile it on + - pillar of the community + - pin your hopes on + - pitter patter of little feet + - plain as day + - plain as the nose on your face + - play by the rules + - play your cards right + - playing the field + - playing with fire + - pleased as punch + - plenty of fish in the sea + - point with pride + - poor as a church mouse + - pot calling the kettle black + - pretty as a picture + - pull a fast one + - pull your punches + - pulling your leg + - pure as the driven snow + - put it in a nutshell + - put one over on you + - put the cart before the horse + - put the pedal to the metal + - put your best foot forward + - put your foot down + - quick as a bunny + - quick as a lick + - quick as a wink + - quick as lightning + - quiet as a dormouse + - rags to riches + - raining buckets + - raining cats and dogs + - rank and file + - rat race + - reap what you sow + - red as a beet + - red herring + - reinvent the wheel + - rich and famous + - rings a bell + - ripe old age + - ripped me off + - rise and shine + - road to hell is paved with good intentions + - rob Peter to pay Paul + - roll over in the grave + - rub the wrong way + - ruled the roost + - running in circles + - sad but true + - sadder but wiser + - salt of the earth + - scared stiff + - scared to death + - sealed with a kiss + - second to none + - see eye to eye + - seen the light + - seize the day + - set the record straight + - set the world on fire + - set your teeth on edge + - sharp as a tack + - shoot for the moon + - shoot the breeze + - shot in the dark + - shoulder to the wheel + - sick as a dog + - sigh of relief + - signed, sealed, and delivered + - sink or swim + - six of one, half a dozen of another + - skating on thin ice + - slept like a log + - slinging mud + - slippery as an eel + - slow as molasses + - smart as a whip + - smooth as a baby's bottom + - sneaking suspicion + - snug as a bug in a rug + - sow wild oats + - spare the rod, spoil the child + - speak of the devil + - spilled the beans + - spinning your wheels + - spitting image of + - spoke with relish + - spread like wildfire + - spring to life + - squeaky wheel gets the grease + - stands out like a sore thumb + - start from scratch + - stick in the mud + - still waters run deep + - stitch in time + - stop and smell the roses + - straight as an arrow + - straw that broke the camel's back + - strong as an ox + - stubborn as a mule + - stuff that dreams are made of + - stuffed shirt + - sweating blood + - sweating bullets + - take a load off + - take one for the team + - take the bait + - take the bull by the horns + - take the plunge + - takes one to know one + - takes two to tango + - the more the merrier + - the real deal + - the real McCoy + - the red carpet treatment + - the same old story + - there is no accounting for taste + - thick as a brick + - thick as thieves + - thin as a rail + - think outside of the box + - third time's the charm + - this day and age + - this hurts me worse than it hurts you + - this point in time + - three sheets to the wind + - through thick and thin + - throw in the towel + - tie one on + - tighter than a drum + - time and time again + - time is of the essence + - tip of the iceberg + - tired but happy + - to coin a phrase + - to each his own + - to make a long story short + - to the best of my knowledge + - toe the line + - tongue in cheek + - too good to be true + - too hot to handle + - too numerous to mention + - touch with a ten foot pole + - tough as nails + - trial and error + - trials and tribulations + - tried and true + - trip down memory lane + - twist of fate + - two cents worth + - two peas in a pod + - ugly as sin + - under the counter + - under the gun + - under the same roof + - under the weather + - until the cows come home + - unvarnished truth + - up the creek + - uphill battle + - upper crust + - upset the applecart + - vain attempt + - vain effort + - vanquish the enemy + - vested interest + - waiting for the other shoe to drop + - wakeup call + - warm welcome + - watch your p's and q's + - watch your tongue + - watching the clock + - water under the bridge + - weather the storm + - weed them out + - week of Sundays + - went belly up + - wet behind the ears + - what goes around comes around + - what you see is what you get + - when it rains, it pours + - when push comes to shove + - when the cat's away + - when the going gets tough, the tough get going + - white as a sheet + - whole ball of wax + - whole hog + - whole nine yards + - wild goose chase + - will wonders never cease? + - wisdom of the ages + - wise as an owl + - wolf at the door + - words fail me + - work like a dog + - world weary + - worst nightmare + - worth its weight in gold + - wrong side of the bed + - yanking your chain + - yappy as a dog + - years young + - you are what you eat + - you can run but you can't hide + - you only live once + - you're the boss + - young and foolish + - young and vibrant diff --git a/styles/write-good/E-Prime.yml b/styles/write-good/E-Prime.yml new file mode 100644 index 00000000..074a102b --- /dev/null +++ b/styles/write-good/E-Prime.yml @@ -0,0 +1,32 @@ +extends: existence +message: "Try to avoid using '%s'." +ignorecase: true +level: suggestion +tokens: + - am + - are + - aren't + - be + - been + - being + - he's + - here's + - here's + - how's + - i'm + - is + - isn't + - it's + - she's + - that's + - there's + - they're + - was + - wasn't + - we're + - were + - weren't + - what's + - where's + - who's + - you're diff --git a/styles/write-good/Illusions.yml b/styles/write-good/Illusions.yml new file mode 100644 index 00000000..b4f13218 --- /dev/null +++ b/styles/write-good/Illusions.yml @@ -0,0 +1,11 @@ +extends: repetition +message: "'%s' is repeated!" +level: warning +alpha: true +action: + name: edit + params: + - truncate + - " " +tokens: + - '[^\s]+' diff --git a/styles/write-good/Passive.yml b/styles/write-good/Passive.yml new file mode 100644 index 00000000..f472cb90 --- /dev/null +++ b/styles/write-good/Passive.yml @@ -0,0 +1,183 @@ +extends: existence +message: "'%s' may be passive voice. Use active voice if you can." +ignorecase: true +level: warning +raw: + - \b(am|are|were|being|is|been|was|be)\b\s* +tokens: + - '[\w]+ed' + - awoken + - beat + - become + - been + - begun + - bent + - beset + - bet + - bid + - bidden + - bitten + - bled + - blown + - born + - bought + - bound + - bred + - broadcast + - broken + - brought + - built + - burnt + - burst + - cast + - caught + - chosen + - clung + - come + - cost + - crept + - cut + - dealt + - dived + - done + - drawn + - dreamt + - driven + - drunk + - dug + - eaten + - fallen + - fed + - felt + - fit + - fled + - flown + - flung + - forbidden + - foregone + - forgiven + - forgotten + - forsaken + - fought + - found + - frozen + - given + - gone + - gotten + - ground + - grown + - heard + - held + - hidden + - hit + - hung + - hurt + - kept + - knelt + - knit + - known + - laid + - lain + - leapt + - learnt + - led + - left + - lent + - let + - lighted + - lost + - made + - meant + - met + - misspelt + - mistaken + - mown + - overcome + - overdone + - overtaken + - overthrown + - paid + - pled + - proven + - put + - quit + - read + - rid + - ridden + - risen + - run + - rung + - said + - sat + - sawn + - seen + - sent + - set + - sewn + - shaken + - shaven + - shed + - shod + - shone + - shorn + - shot + - shown + - shrunk + - shut + - slain + - slept + - slid + - slit + - slung + - smitten + - sold + - sought + - sown + - sped + - spent + - spilt + - spit + - split + - spoken + - spread + - sprung + - spun + - stolen + - stood + - stridden + - striven + - struck + - strung + - stuck + - stung + - stunk + - sung + - sunk + - swept + - swollen + - sworn + - swum + - swung + - taken + - taught + - thought + - thrived + - thrown + - thrust + - told + - torn + - trodden + - understood + - upheld + - upset + - wed + - wept + - withheld + - withstood + - woken + - won + - worn + - wound + - woven + - written + - wrung diff --git a/styles/write-good/README.md b/styles/write-good/README.md new file mode 100644 index 00000000..3edcc9b3 --- /dev/null +++ b/styles/write-good/README.md @@ -0,0 +1,27 @@ +Based on [write-good](https://github.com/btford/write-good). + +> Naive linter for English prose for developers who can't write good and wanna learn to do other stuff good too. + +``` +The MIT License (MIT) + +Copyright (c) 2014 Brian Ford + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` diff --git a/styles/write-good/So.yml b/styles/write-good/So.yml new file mode 100644 index 00000000..e57f099d --- /dev/null +++ b/styles/write-good/So.yml @@ -0,0 +1,5 @@ +extends: existence +message: "Don't start a sentence with '%s'." +level: error +raw: + - '(?:[;-]\s)so[\s,]|\bSo[\s,]' diff --git a/styles/write-good/ThereIs.yml b/styles/write-good/ThereIs.yml new file mode 100644 index 00000000..8b82e8f6 --- /dev/null +++ b/styles/write-good/ThereIs.yml @@ -0,0 +1,6 @@ +extends: existence +message: "Don't start a sentence with '%s'." +ignorecase: false +level: error +raw: + - '(?:[;-]\s)There\s(is|are)|\bThere\s(is|are)\b' diff --git a/styles/write-good/TooWordy.yml b/styles/write-good/TooWordy.yml new file mode 100644 index 00000000..275701b1 --- /dev/null +++ b/styles/write-good/TooWordy.yml @@ -0,0 +1,221 @@ +extends: existence +message: "'%s' is too wordy." +ignorecase: true +level: warning +tokens: + - a number of + - abundance + - accede to + - accelerate + - accentuate + - accompany + - accomplish + - accorded + - accrue + - acquiesce + - acquire + - additional + - adjacent to + - adjustment + - admissible + - advantageous + - adversely impact + - advise + - aforementioned + - aggregate + - aircraft + - all of + - all things considered + - alleviate + - allocate + - along the lines of + - already existing + - alternatively + - amazing + - ameliorate + - anticipate + - apparent + - appreciable + - as a matter of fact + - as a means of + - as far as I'm concerned + - as of yet + - as to + - as yet + - ascertain + - assistance + - at the present time + - at this time + - attain + - attributable to + - authorize + - because of the fact that + - belated + - benefit from + - bestow + - by means of + - by virtue of + - by virtue of the fact that + - cease + - close proximity + - commence + - comply with + - concerning + - consequently + - consolidate + - constitutes + - demonstrate + - depart + - designate + - discontinue + - due to the fact that + - each and every + - economical + - eliminate + - elucidate + - employ + - endeavor + - enumerate + - equitable + - equivalent + - evaluate + - evidenced + - exclusively + - expedite + - expend + - expiration + - facilitate + - factual evidence + - feasible + - finalize + - first and foremost + - for all intents and purposes + - for the most part + - for the purpose of + - forfeit + - formulate + - have a tendency to + - honest truth + - however + - if and when + - impacted + - implement + - in a manner of speaking + - in a timely manner + - in a very real sense + - in accordance with + - in addition + - in all likelihood + - in an effort to + - in between + - in excess of + - in lieu of + - in light of the fact that + - in many cases + - in my opinion + - in order to + - in regard to + - in some instances + - in terms of + - in the case of + - in the event that + - in the final analysis + - in the nature of + - in the near future + - in the process of + - inception + - incumbent upon + - indicate + - indication + - initiate + - irregardless + - is applicable to + - is authorized to + - is responsible for + - it is + - it is essential + - it seems that + - it was + - magnitude + - maximum + - methodology + - minimize + - minimum + - modify + - monitor + - multiple + - necessitate + - nevertheless + - not certain + - not many + - not often + - not unless + - not unlike + - notwithstanding + - null and void + - numerous + - objective + - obligate + - obtain + - on the contrary + - on the other hand + - one particular + - optimum + - overall + - owing to the fact that + - participate + - particulars + - pass away + - pertaining to + - point in time + - portion + - possess + - preclude + - previously + - prior to + - prioritize + - procure + - proficiency + - provided that + - purchase + - put simply + - readily apparent + - refer back + - regarding + - relocate + - remainder + - remuneration + - requirement + - reside + - residence + - retain + - satisfy + - shall + - should you wish + - similar to + - solicit + - span across + - strategize + - subsequent + - substantial + - successfully complete + - sufficient + - terminate + - the month of + - the point I am trying to make + - therefore + - time period + - took advantage of + - transmit + - transpire + - type of + - until such time as + - utilization + - utilize + - validate + - various different + - what I mean to say is + - whether or not + - with respect to + - with the exception of + - witnessed diff --git a/styles/write-good/Weasel.yml b/styles/write-good/Weasel.yml new file mode 100644 index 00000000..d1d90a7b --- /dev/null +++ b/styles/write-good/Weasel.yml @@ -0,0 +1,29 @@ +extends: existence +message: "'%s' is a weasel word!" +ignorecase: true +level: warning +tokens: + - clearly + - completely + - exceedingly + - excellent + - extremely + - fairly + - huge + - interestingly + - is a number + - largely + - mostly + - obviously + - quite + - relatively + - remarkably + - several + - significantly + - substantially + - surprisingly + - tiny + - usually + - various + - vast + - very diff --git a/styles/write-good/meta.json b/styles/write-good/meta.json new file mode 100644 index 00000000..a115d288 --- /dev/null +++ b/styles/write-good/meta.json @@ -0,0 +1,4 @@ +{ + "feed": "https://github.com/errata-ai/write-good/releases.atom", + "vale_version": ">=1.0.0" +} From 9a08a46119f7d77355bdb99dc81d30b288a9ddff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20Ainsworth?= Date: Mon, 16 Mar 2026 06:50:36 +0900 Subject: [PATCH 3/7] Fix formatting issue --- src/content/docs/specs/subscriptions.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/content/docs/specs/subscriptions.md b/src/content/docs/specs/subscriptions.md index f4b68251..fd914f7d 100644 --- a/src/content/docs/specs/subscriptions.md +++ b/src/content/docs/specs/subscriptions.md @@ -50,7 +50,8 @@ User Feed : A shared resource representing a podcast feed. -Subscription: A user-owned resource containing details about a User's subscription to a Feed. +Subscription +: A user-owned resource containing details about a User's subscription to a Feed. Action : An operation performed against a subscription resource. From b1ca2432540096d39370ef900fd5cc15e59bb237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20Ainsworth?= Date: Mon, 16 Mar 2026 06:53:36 +0900 Subject: [PATCH 4/7] Remove Vale from PR --- .vale.ini | 7 - styles/.vale-config/2-MDX.ini | 5 - styles/Google/AMPM.yml | 9 - styles/Google/Acronyms.yml | 64 --- styles/Google/Colons.yml | 8 - styles/Google/Contractions.yml | 30 -- styles/Google/DateFormat.yml | 9 - styles/Google/Ellipses.yml | 9 - styles/Google/EmDash.yml | 13 - styles/Google/Exclamation.yml | 12 - styles/Google/FirstPerson.yml | 13 - styles/Google/Gender.yml | 9 - styles/Google/GenderBias.yml | 43 -- styles/Google/HeadingPunctuation.yml | 13 - styles/Google/Headings.yml | 29 -- styles/Google/Latin.yml | 11 - styles/Google/LyHyphens.yml | 14 - styles/Google/OptionalPlurals.yml | 12 - styles/Google/Ordinal.yml | 7 - styles/Google/OxfordComma.yml | 7 - styles/Google/Parens.yml | 7 - styles/Google/Passive.yml | 184 ------- styles/Google/Periods.yml | 7 - styles/Google/Quotes.yml | 7 - styles/Google/Ranges.yml | 7 - styles/Google/Semicolons.yml | 8 - styles/Google/Slang.yml | 11 - styles/Google/Spacing.yml | 10 - styles/Google/Spelling.yml | 10 - styles/Google/Units.yml | 8 - styles/Google/We.yml | 11 - styles/Google/Will.yml | 7 - styles/Google/WordList.yml | 80 --- styles/Google/meta.json | 4 - styles/Google/vocab.txt | 0 styles/write-good/Cliches.yml | 702 --------------------------- styles/write-good/E-Prime.yml | 32 -- styles/write-good/Illusions.yml | 11 - styles/write-good/Passive.yml | 183 ------- styles/write-good/README.md | 27 -- styles/write-good/So.yml | 5 - styles/write-good/ThereIs.yml | 6 - styles/write-good/TooWordy.yml | 221 --------- styles/write-good/Weasel.yml | 29 -- styles/write-good/meta.json | 4 - 45 files changed, 1905 deletions(-) delete mode 100644 .vale.ini delete mode 100644 styles/.vale-config/2-MDX.ini delete mode 100644 styles/Google/AMPM.yml delete mode 100644 styles/Google/Acronyms.yml delete mode 100644 styles/Google/Colons.yml delete mode 100644 styles/Google/Contractions.yml delete mode 100644 styles/Google/DateFormat.yml delete mode 100644 styles/Google/Ellipses.yml delete mode 100644 styles/Google/EmDash.yml delete mode 100644 styles/Google/Exclamation.yml delete mode 100644 styles/Google/FirstPerson.yml delete mode 100644 styles/Google/Gender.yml delete mode 100644 styles/Google/GenderBias.yml delete mode 100644 styles/Google/HeadingPunctuation.yml delete mode 100644 styles/Google/Headings.yml delete mode 100644 styles/Google/Latin.yml delete mode 100644 styles/Google/LyHyphens.yml delete mode 100644 styles/Google/OptionalPlurals.yml delete mode 100644 styles/Google/Ordinal.yml delete mode 100644 styles/Google/OxfordComma.yml delete mode 100644 styles/Google/Parens.yml delete mode 100644 styles/Google/Passive.yml delete mode 100644 styles/Google/Periods.yml delete mode 100644 styles/Google/Quotes.yml delete mode 100644 styles/Google/Ranges.yml delete mode 100644 styles/Google/Semicolons.yml delete mode 100644 styles/Google/Slang.yml delete mode 100644 styles/Google/Spacing.yml delete mode 100644 styles/Google/Spelling.yml delete mode 100644 styles/Google/Units.yml delete mode 100644 styles/Google/We.yml delete mode 100644 styles/Google/Will.yml delete mode 100644 styles/Google/WordList.yml delete mode 100644 styles/Google/meta.json delete mode 100644 styles/Google/vocab.txt delete mode 100644 styles/write-good/Cliches.yml delete mode 100644 styles/write-good/E-Prime.yml delete mode 100644 styles/write-good/Illusions.yml delete mode 100644 styles/write-good/Passive.yml delete mode 100644 styles/write-good/README.md delete mode 100644 styles/write-good/So.yml delete mode 100644 styles/write-good/ThereIs.yml delete mode 100644 styles/write-good/TooWordy.yml delete mode 100644 styles/write-good/Weasel.yml delete mode 100644 styles/write-good/meta.json diff --git a/.vale.ini b/.vale.ini deleted file mode 100644 index 7f70c7fc..00000000 --- a/.vale.ini +++ /dev/null @@ -1,7 +0,0 @@ -StylesPath = styles -MinAlertLevel = error - -Packages = Google, write-good, MDX - -[*.{md,mdx}] -BasedOnStyles = Vale, Google, write-good diff --git a/styles/.vale-config/2-MDX.ini b/styles/.vale-config/2-MDX.ini deleted file mode 100644 index 02ea72b3..00000000 --- a/styles/.vale-config/2-MDX.ini +++ /dev/null @@ -1,5 +0,0 @@ -[*.mdx] -# Exclude: -# -# - Non-JS inline expressions (which cause Acorn to throw) -TokenIgnores = '({#[^\n}]+})(?!`)' diff --git a/styles/Google/AMPM.yml b/styles/Google/AMPM.yml deleted file mode 100644 index 37b49edf..00000000 --- a/styles/Google/AMPM.yml +++ /dev/null @@ -1,9 +0,0 @@ -extends: existence -message: "Use 'AM' or 'PM' (preceded by a space)." -link: "https://developers.google.com/style/word-list" -level: error -nonword: true -tokens: - - '\d{1,2}[AP]M\b' - - '\d{1,2} ?[ap]m\b' - - '\d{1,2} ?[aApP]\.[mM]\.' diff --git a/styles/Google/Acronyms.yml b/styles/Google/Acronyms.yml deleted file mode 100644 index f41af018..00000000 --- a/styles/Google/Acronyms.yml +++ /dev/null @@ -1,64 +0,0 @@ -extends: conditional -message: "Spell out '%s', if it's unfamiliar to the audience." -link: 'https://developers.google.com/style/abbreviations' -level: suggestion -ignorecase: false -# Ensures that the existence of 'first' implies the existence of 'second'. -first: '\b([A-Z]{3,5})\b' -second: '(?:\b[A-Z][a-z]+ )+\(([A-Z]{3,5})\)' -# ... with the exception of these: -exceptions: - - API - - ASP - - CLI - - CPU - - CSS - - CSV - - DEBUG - - DOM - - DPI - - FAQ - - GCC - - GDB - - GET - - GPU - - GTK - - GUI - - HTML - - HTTP - - HTTPS - - IDE - - JAR - - JSON - - JSX - - LESS - - LLDB - - NET - - NOTE - - NVDA - - OSS - - PATH - - PDF - - PHP - - POST - - RAM - - REPL - - RSA - - SCM - - SCSS - - SDK - - SQL - - SSH - - SSL - - SVG - - TBD - - TCP - - TODO - - URI - - URL - - USB - - UTF - - XML - - XSS - - YAML - - ZIP diff --git a/styles/Google/Colons.yml b/styles/Google/Colons.yml deleted file mode 100644 index 4a027c30..00000000 --- a/styles/Google/Colons.yml +++ /dev/null @@ -1,8 +0,0 @@ -extends: existence -message: "'%s' should be in lowercase." -link: 'https://developers.google.com/style/colons' -nonword: true -level: warning -scope: sentence -tokens: - - '(?=1.0.0" -} diff --git a/styles/Google/vocab.txt b/styles/Google/vocab.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/styles/write-good/Cliches.yml b/styles/write-good/Cliches.yml deleted file mode 100644 index c9531438..00000000 --- a/styles/write-good/Cliches.yml +++ /dev/null @@ -1,702 +0,0 @@ -extends: existence -message: "Try to avoid using clichés like '%s'." -ignorecase: true -level: warning -tokens: - - a chip off the old block - - a clean slate - - a dark and stormy night - - a far cry - - a fine kettle of fish - - a loose cannon - - a penny saved is a penny earned - - a tough row to hoe - - a word to the wise - - ace in the hole - - acid test - - add insult to injury - - against all odds - - air your dirty laundry - - all fun and games - - all in a day's work - - all talk, no action - - all thumbs - - all your eggs in one basket - - all's fair in love and war - - all's well that ends well - - almighty dollar - - American as apple pie - - an axe to grind - - another day, another dollar - - armed to the teeth - - as luck would have it - - as old as time - - as the crow flies - - at loose ends - - at my wits end - - avoid like the plague - - babe in the woods - - back against the wall - - back in the saddle - - back to square one - - back to the drawing board - - bad to the bone - - badge of honor - - bald faced liar - - ballpark figure - - banging your head against a brick wall - - baptism by fire - - barking up the wrong tree - - bat out of hell - - be all and end all - - beat a dead horse - - beat around the bush - - been there, done that - - beggars can't be choosers - - behind the eight ball - - bend over backwards - - benefit of the doubt - - bent out of shape - - best thing since sliced bread - - bet your bottom dollar - - better half - - better late than never - - better mousetrap - - better safe than sorry - - between a rock and a hard place - - beyond the pale - - bide your time - - big as life - - big cheese - - big fish in a small pond - - big man on campus - - bigger they are the harder they fall - - bird in the hand - - bird's eye view - - birds and the bees - - birds of a feather flock together - - bit the hand that feeds you - - bite the bullet - - bite the dust - - bitten off more than he can chew - - black as coal - - black as pitch - - black as the ace of spades - - blast from the past - - bleeding heart - - blessing in disguise - - blind ambition - - blind as a bat - - blind leading the blind - - blood is thicker than water - - blood sweat and tears - - blow off steam - - blow your own horn - - blushing bride - - boils down to - - bolt from the blue - - bone to pick - - bored stiff - - bored to tears - - bottomless pit - - boys will be boys - - bright and early - - brings home the bacon - - broad across the beam - - broken record - - brought back to reality - - bull by the horns - - bull in a china shop - - burn the midnight oil - - burning question - - burning the candle at both ends - - burst your bubble - - bury the hatchet - - busy as a bee - - by hook or by crook - - call a spade a spade - - called onto the carpet - - calm before the storm - - can of worms - - can't cut the mustard - - can't hold a candle to - - case of mistaken identity - - cat got your tongue - - cat's meow - - caught in the crossfire - - caught red-handed - - checkered past - - chomping at the bit - - cleanliness is next to godliness - - clear as a bell - - clear as mud - - close to the vest - - cock and bull story - - cold shoulder - - come hell or high water - - cool as a cucumber - - cool, calm, and collected - - cost a king's ransom - - count your blessings - - crack of dawn - - crash course - - creature comforts - - cross that bridge when you come to it - - crushing blow - - cry like a baby - - cry me a river - - cry over spilt milk - - crystal clear - - curiosity killed the cat - - cut and dried - - cut through the red tape - - cut to the chase - - cute as a bugs ear - - cute as a button - - cute as a puppy - - cuts to the quick - - dark before the dawn - - day in, day out - - dead as a doornail - - devil is in the details - - dime a dozen - - divide and conquer - - dog and pony show - - dog days - - dog eat dog - - dog tired - - don't burn your bridges - - don't count your chickens - - don't look a gift horse in the mouth - - don't rock the boat - - don't step on anyone's toes - - don't take any wooden nickels - - down and out - - down at the heels - - down in the dumps - - down the hatch - - down to earth - - draw the line - - dressed to kill - - dressed to the nines - - drives me up the wall - - dull as dishwater - - dyed in the wool - - eagle eye - - ear to the ground - - early bird catches the worm - - easier said than done - - easy as pie - - eat your heart out - - eat your words - - eleventh hour - - even the playing field - - every dog has its day - - every fiber of my being - - everything but the kitchen sink - - eye for an eye - - face the music - - facts of life - - fair weather friend - - fall by the wayside - - fan the flames - - feast or famine - - feather your nest - - feathered friends - - few and far between - - fifteen minutes of fame - - filthy vermin - - fine kettle of fish - - fish out of water - - fishing for a compliment - - fit as a fiddle - - fit the bill - - fit to be tied - - flash in the pan - - flat as a pancake - - flip your lid - - flog a dead horse - - fly by night - - fly the coop - - follow your heart - - for all intents and purposes - - for the birds - - for what it's worth - - force of nature - - force to be reckoned with - - forgive and forget - - fox in the henhouse - - free and easy - - free as a bird - - fresh as a daisy - - full steam ahead - - fun in the sun - - garbage in, garbage out - - gentle as a lamb - - get a kick out of - - get a leg up - - get down and dirty - - get the lead out - - get to the bottom of - - get your feet wet - - gets my goat - - gilding the lily - - give and take - - go against the grain - - go at it tooth and nail - - go for broke - - go him one better - - go the extra mile - - go with the flow - - goes without saying - - good as gold - - good deed for the day - - good things come to those who wait - - good time was had by all - - good times were had by all - - greased lightning - - greek to me - - green thumb - - green-eyed monster - - grist for the mill - - growing like a weed - - hair of the dog - - hand to mouth - - happy as a clam - - happy as a lark - - hasn't a clue - - have a nice day - - have high hopes - - have the last laugh - - haven't got a row to hoe - - head honcho - - head over heels - - hear a pin drop - - heard it through the grapevine - - heart's content - - heavy as lead - - hem and haw - - high and dry - - high and mighty - - high as a kite - - hit paydirt - - hold your head up high - - hold your horses - - hold your own - - hold your tongue - - honest as the day is long - - horns of a dilemma - - horse of a different color - - hot under the collar - - hour of need - - I beg to differ - - icing on the cake - - if the shoe fits - - if the shoe were on the other foot - - in a jam - - in a jiffy - - in a nutshell - - in a pig's eye - - in a pinch - - in a word - - in hot water - - in the gutter - - in the nick of time - - in the thick of it - - in your dreams - - it ain't over till the fat lady sings - - it goes without saying - - it takes all kinds - - it takes one to know one - - it's a small world - - it's only a matter of time - - ivory tower - - Jack of all trades - - jockey for position - - jog your memory - - joined at the hip - - judge a book by its cover - - jump down your throat - - jump in with both feet - - jump on the bandwagon - - jump the gun - - jump to conclusions - - just a hop, skip, and a jump - - just the ticket - - justice is blind - - keep a stiff upper lip - - keep an eye on - - keep it simple, stupid - - keep the home fires burning - - keep up with the Joneses - - keep your chin up - - keep your fingers crossed - - kick the bucket - - kick up your heels - - kick your feet up - - kid in a candy store - - kill two birds with one stone - - kiss of death - - knock it out of the park - - knock on wood - - knock your socks off - - know him from Adam - - know the ropes - - know the score - - knuckle down - - knuckle sandwich - - knuckle under - - labor of love - - ladder of success - - land on your feet - - lap of luxury - - last but not least - - last hurrah - - last-ditch effort - - law of the jungle - - law of the land - - lay down the law - - leaps and bounds - - let sleeping dogs lie - - let the cat out of the bag - - let the good times roll - - let your hair down - - let's talk turkey - - letter perfect - - lick your wounds - - lies like a rug - - life's a bitch - - life's a grind - - light at the end of the tunnel - - lighter than a feather - - lighter than air - - like clockwork - - like father like son - - like taking candy from a baby - - like there's no tomorrow - - lion's share - - live and learn - - live and let live - - long and short of it - - long lost love - - look before you leap - - look down your nose - - look what the cat dragged in - - looking a gift horse in the mouth - - looks like death warmed over - - loose cannon - - lose your head - - lose your temper - - loud as a horn - - lounge lizard - - loved and lost - - low man on the totem pole - - luck of the draw - - luck of the Irish - - make hay while the sun shines - - make money hand over fist - - make my day - - make the best of a bad situation - - make the best of it - - make your blood boil - - man of few words - - man's best friend - - mark my words - - meaningful dialogue - - missed the boat on that one - - moment in the sun - - moment of glory - - moment of truth - - money to burn - - more power to you - - more than one way to skin a cat - - movers and shakers - - moving experience - - naked as a jaybird - - naked truth - - neat as a pin - - needle in a haystack - - needless to say - - neither here nor there - - never look back - - never say never - - nip and tuck - - nip it in the bud - - no guts, no glory - - no love lost - - no pain, no gain - - no skin off my back - - no stone unturned - - no time like the present - - no use crying over spilled milk - - nose to the grindstone - - not a hope in hell - - not a minute's peace - - not in my backyard - - not playing with a full deck - - not the end of the world - - not written in stone - - nothing to sneeze at - - nothing ventured nothing gained - - now we're cooking - - off the top of my head - - off the wagon - - off the wall - - old hat - - older and wiser - - older than dirt - - older than Methuselah - - on a roll - - on cloud nine - - on pins and needles - - on the bandwagon - - on the money - - on the nose - - on the rocks - - on the spot - - on the tip of my tongue - - on the wagon - - on thin ice - - once bitten, twice shy - - one bad apple doesn't spoil the bushel - - one born every minute - - one brick short - - one foot in the grave - - one in a million - - one red cent - - only game in town - - open a can of worms - - open and shut case - - open the flood gates - - opportunity doesn't knock twice - - out of pocket - - out of sight, out of mind - - out of the frying pan into the fire - - out of the woods - - out on a limb - - over a barrel - - over the hump - - pain and suffering - - pain in the - - panic button - - par for the course - - part and parcel - - party pooper - - pass the buck - - patience is a virtue - - pay through the nose - - penny pincher - - perfect storm - - pig in a poke - - pile it on - - pillar of the community - - pin your hopes on - - pitter patter of little feet - - plain as day - - plain as the nose on your face - - play by the rules - - play your cards right - - playing the field - - playing with fire - - pleased as punch - - plenty of fish in the sea - - point with pride - - poor as a church mouse - - pot calling the kettle black - - pretty as a picture - - pull a fast one - - pull your punches - - pulling your leg - - pure as the driven snow - - put it in a nutshell - - put one over on you - - put the cart before the horse - - put the pedal to the metal - - put your best foot forward - - put your foot down - - quick as a bunny - - quick as a lick - - quick as a wink - - quick as lightning - - quiet as a dormouse - - rags to riches - - raining buckets - - raining cats and dogs - - rank and file - - rat race - - reap what you sow - - red as a beet - - red herring - - reinvent the wheel - - rich and famous - - rings a bell - - ripe old age - - ripped me off - - rise and shine - - road to hell is paved with good intentions - - rob Peter to pay Paul - - roll over in the grave - - rub the wrong way - - ruled the roost - - running in circles - - sad but true - - sadder but wiser - - salt of the earth - - scared stiff - - scared to death - - sealed with a kiss - - second to none - - see eye to eye - - seen the light - - seize the day - - set the record straight - - set the world on fire - - set your teeth on edge - - sharp as a tack - - shoot for the moon - - shoot the breeze - - shot in the dark - - shoulder to the wheel - - sick as a dog - - sigh of relief - - signed, sealed, and delivered - - sink or swim - - six of one, half a dozen of another - - skating on thin ice - - slept like a log - - slinging mud - - slippery as an eel - - slow as molasses - - smart as a whip - - smooth as a baby's bottom - - sneaking suspicion - - snug as a bug in a rug - - sow wild oats - - spare the rod, spoil the child - - speak of the devil - - spilled the beans - - spinning your wheels - - spitting image of - - spoke with relish - - spread like wildfire - - spring to life - - squeaky wheel gets the grease - - stands out like a sore thumb - - start from scratch - - stick in the mud - - still waters run deep - - stitch in time - - stop and smell the roses - - straight as an arrow - - straw that broke the camel's back - - strong as an ox - - stubborn as a mule - - stuff that dreams are made of - - stuffed shirt - - sweating blood - - sweating bullets - - take a load off - - take one for the team - - take the bait - - take the bull by the horns - - take the plunge - - takes one to know one - - takes two to tango - - the more the merrier - - the real deal - - the real McCoy - - the red carpet treatment - - the same old story - - there is no accounting for taste - - thick as a brick - - thick as thieves - - thin as a rail - - think outside of the box - - third time's the charm - - this day and age - - this hurts me worse than it hurts you - - this point in time - - three sheets to the wind - - through thick and thin - - throw in the towel - - tie one on - - tighter than a drum - - time and time again - - time is of the essence - - tip of the iceberg - - tired but happy - - to coin a phrase - - to each his own - - to make a long story short - - to the best of my knowledge - - toe the line - - tongue in cheek - - too good to be true - - too hot to handle - - too numerous to mention - - touch with a ten foot pole - - tough as nails - - trial and error - - trials and tribulations - - tried and true - - trip down memory lane - - twist of fate - - two cents worth - - two peas in a pod - - ugly as sin - - under the counter - - under the gun - - under the same roof - - under the weather - - until the cows come home - - unvarnished truth - - up the creek - - uphill battle - - upper crust - - upset the applecart - - vain attempt - - vain effort - - vanquish the enemy - - vested interest - - waiting for the other shoe to drop - - wakeup call - - warm welcome - - watch your p's and q's - - watch your tongue - - watching the clock - - water under the bridge - - weather the storm - - weed them out - - week of Sundays - - went belly up - - wet behind the ears - - what goes around comes around - - what you see is what you get - - when it rains, it pours - - when push comes to shove - - when the cat's away - - when the going gets tough, the tough get going - - white as a sheet - - whole ball of wax - - whole hog - - whole nine yards - - wild goose chase - - will wonders never cease? - - wisdom of the ages - - wise as an owl - - wolf at the door - - words fail me - - work like a dog - - world weary - - worst nightmare - - worth its weight in gold - - wrong side of the bed - - yanking your chain - - yappy as a dog - - years young - - you are what you eat - - you can run but you can't hide - - you only live once - - you're the boss - - young and foolish - - young and vibrant diff --git a/styles/write-good/E-Prime.yml b/styles/write-good/E-Prime.yml deleted file mode 100644 index 074a102b..00000000 --- a/styles/write-good/E-Prime.yml +++ /dev/null @@ -1,32 +0,0 @@ -extends: existence -message: "Try to avoid using '%s'." -ignorecase: true -level: suggestion -tokens: - - am - - are - - aren't - - be - - been - - being - - he's - - here's - - here's - - how's - - i'm - - is - - isn't - - it's - - she's - - that's - - there's - - they're - - was - - wasn't - - we're - - were - - weren't - - what's - - where's - - who's - - you're diff --git a/styles/write-good/Illusions.yml b/styles/write-good/Illusions.yml deleted file mode 100644 index b4f13218..00000000 --- a/styles/write-good/Illusions.yml +++ /dev/null @@ -1,11 +0,0 @@ -extends: repetition -message: "'%s' is repeated!" -level: warning -alpha: true -action: - name: edit - params: - - truncate - - " " -tokens: - - '[^\s]+' diff --git a/styles/write-good/Passive.yml b/styles/write-good/Passive.yml deleted file mode 100644 index f472cb90..00000000 --- a/styles/write-good/Passive.yml +++ /dev/null @@ -1,183 +0,0 @@ -extends: existence -message: "'%s' may be passive voice. Use active voice if you can." -ignorecase: true -level: warning -raw: - - \b(am|are|were|being|is|been|was|be)\b\s* -tokens: - - '[\w]+ed' - - awoken - - beat - - become - - been - - begun - - bent - - beset - - bet - - bid - - bidden - - bitten - - bled - - blown - - born - - bought - - bound - - bred - - broadcast - - broken - - brought - - built - - burnt - - burst - - cast - - caught - - chosen - - clung - - come - - cost - - crept - - cut - - dealt - - dived - - done - - drawn - - dreamt - - driven - - drunk - - dug - - eaten - - fallen - - fed - - felt - - fit - - fled - - flown - - flung - - forbidden - - foregone - - forgiven - - forgotten - - forsaken - - fought - - found - - frozen - - given - - gone - - gotten - - ground - - grown - - heard - - held - - hidden - - hit - - hung - - hurt - - kept - - knelt - - knit - - known - - laid - - lain - - leapt - - learnt - - led - - left - - lent - - let - - lighted - - lost - - made - - meant - - met - - misspelt - - mistaken - - mown - - overcome - - overdone - - overtaken - - overthrown - - paid - - pled - - proven - - put - - quit - - read - - rid - - ridden - - risen - - run - - rung - - said - - sat - - sawn - - seen - - sent - - set - - sewn - - shaken - - shaven - - shed - - shod - - shone - - shorn - - shot - - shown - - shrunk - - shut - - slain - - slept - - slid - - slit - - slung - - smitten - - sold - - sought - - sown - - sped - - spent - - spilt - - spit - - split - - spoken - - spread - - sprung - - spun - - stolen - - stood - - stridden - - striven - - struck - - strung - - stuck - - stung - - stunk - - sung - - sunk - - swept - - swollen - - sworn - - swum - - swung - - taken - - taught - - thought - - thrived - - thrown - - thrust - - told - - torn - - trodden - - understood - - upheld - - upset - - wed - - wept - - withheld - - withstood - - woken - - won - - worn - - wound - - woven - - written - - wrung diff --git a/styles/write-good/README.md b/styles/write-good/README.md deleted file mode 100644 index 3edcc9b3..00000000 --- a/styles/write-good/README.md +++ /dev/null @@ -1,27 +0,0 @@ -Based on [write-good](https://github.com/btford/write-good). - -> Naive linter for English prose for developers who can't write good and wanna learn to do other stuff good too. - -``` -The MIT License (MIT) - -Copyright (c) 2014 Brian Ford - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -``` diff --git a/styles/write-good/So.yml b/styles/write-good/So.yml deleted file mode 100644 index e57f099d..00000000 --- a/styles/write-good/So.yml +++ /dev/null @@ -1,5 +0,0 @@ -extends: existence -message: "Don't start a sentence with '%s'." -level: error -raw: - - '(?:[;-]\s)so[\s,]|\bSo[\s,]' diff --git a/styles/write-good/ThereIs.yml b/styles/write-good/ThereIs.yml deleted file mode 100644 index 8b82e8f6..00000000 --- a/styles/write-good/ThereIs.yml +++ /dev/null @@ -1,6 +0,0 @@ -extends: existence -message: "Don't start a sentence with '%s'." -ignorecase: false -level: error -raw: - - '(?:[;-]\s)There\s(is|are)|\bThere\s(is|are)\b' diff --git a/styles/write-good/TooWordy.yml b/styles/write-good/TooWordy.yml deleted file mode 100644 index 275701b1..00000000 --- a/styles/write-good/TooWordy.yml +++ /dev/null @@ -1,221 +0,0 @@ -extends: existence -message: "'%s' is too wordy." -ignorecase: true -level: warning -tokens: - - a number of - - abundance - - accede to - - accelerate - - accentuate - - accompany - - accomplish - - accorded - - accrue - - acquiesce - - acquire - - additional - - adjacent to - - adjustment - - admissible - - advantageous - - adversely impact - - advise - - aforementioned - - aggregate - - aircraft - - all of - - all things considered - - alleviate - - allocate - - along the lines of - - already existing - - alternatively - - amazing - - ameliorate - - anticipate - - apparent - - appreciable - - as a matter of fact - - as a means of - - as far as I'm concerned - - as of yet - - as to - - as yet - - ascertain - - assistance - - at the present time - - at this time - - attain - - attributable to - - authorize - - because of the fact that - - belated - - benefit from - - bestow - - by means of - - by virtue of - - by virtue of the fact that - - cease - - close proximity - - commence - - comply with - - concerning - - consequently - - consolidate - - constitutes - - demonstrate - - depart - - designate - - discontinue - - due to the fact that - - each and every - - economical - - eliminate - - elucidate - - employ - - endeavor - - enumerate - - equitable - - equivalent - - evaluate - - evidenced - - exclusively - - expedite - - expend - - expiration - - facilitate - - factual evidence - - feasible - - finalize - - first and foremost - - for all intents and purposes - - for the most part - - for the purpose of - - forfeit - - formulate - - have a tendency to - - honest truth - - however - - if and when - - impacted - - implement - - in a manner of speaking - - in a timely manner - - in a very real sense - - in accordance with - - in addition - - in all likelihood - - in an effort to - - in between - - in excess of - - in lieu of - - in light of the fact that - - in many cases - - in my opinion - - in order to - - in regard to - - in some instances - - in terms of - - in the case of - - in the event that - - in the final analysis - - in the nature of - - in the near future - - in the process of - - inception - - incumbent upon - - indicate - - indication - - initiate - - irregardless - - is applicable to - - is authorized to - - is responsible for - - it is - - it is essential - - it seems that - - it was - - magnitude - - maximum - - methodology - - minimize - - minimum - - modify - - monitor - - multiple - - necessitate - - nevertheless - - not certain - - not many - - not often - - not unless - - not unlike - - notwithstanding - - null and void - - numerous - - objective - - obligate - - obtain - - on the contrary - - on the other hand - - one particular - - optimum - - overall - - owing to the fact that - - participate - - particulars - - pass away - - pertaining to - - point in time - - portion - - possess - - preclude - - previously - - prior to - - prioritize - - procure - - proficiency - - provided that - - purchase - - put simply - - readily apparent - - refer back - - regarding - - relocate - - remainder - - remuneration - - requirement - - reside - - residence - - retain - - satisfy - - shall - - should you wish - - similar to - - solicit - - span across - - strategize - - subsequent - - substantial - - successfully complete - - sufficient - - terminate - - the month of - - the point I am trying to make - - therefore - - time period - - took advantage of - - transmit - - transpire - - type of - - until such time as - - utilization - - utilize - - validate - - various different - - what I mean to say is - - whether or not - - with respect to - - with the exception of - - witnessed diff --git a/styles/write-good/Weasel.yml b/styles/write-good/Weasel.yml deleted file mode 100644 index d1d90a7b..00000000 --- a/styles/write-good/Weasel.yml +++ /dev/null @@ -1,29 +0,0 @@ -extends: existence -message: "'%s' is a weasel word!" -ignorecase: true -level: warning -tokens: - - clearly - - completely - - exceedingly - - excellent - - extremely - - fairly - - huge - - interestingly - - is a number - - largely - - mostly - - obviously - - quite - - relatively - - remarkably - - several - - significantly - - substantially - - surprisingly - - tiny - - usually - - various - - vast - - very diff --git a/styles/write-good/meta.json b/styles/write-good/meta.json deleted file mode 100644 index a115d288..00000000 --- a/styles/write-good/meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "feed": "https://github.com/errata-ai/write-good/releases.atom", - "vale_version": ">=1.0.0" -} From 2df3864482a6fed483c74dde6fbf67266f876c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20Ainsworth?= Date: Mon, 16 Mar 2026 06:59:00 +0900 Subject: [PATCH 5/7] Fix sentence casing --- src/content/docs/specs/subscriptions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/content/docs/specs/subscriptions.md b/src/content/docs/specs/subscriptions.md index fd914f7d..b9cb7204 100644 --- a/src/content/docs/specs/subscriptions.md +++ b/src/content/docs/specs/subscriptions.md @@ -91,7 +91,7 @@ This document does not define: * Feed metadata ingestion * Client user interface behavior -## 5. System Architecture +## 5. System architecture ### 5.1 Overview @@ -100,13 +100,13 @@ The system consists of: * Client devices * An HTTP API server -### 5.2 Offline Operation +### 5.2 Offline operation Clients MAY operate without network connectivity and queue actions locally. Queued actions MUST be transmitted to the server when connectivity is restored. -### 5.3 Synchronization Model +### 5.3 Synchronization model Synchronization is based on an append-only action log. From 88ea10a84994e0b607c219dc7e9337e89269e4ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20Ainsworth?= Date: Mon, 16 Mar 2026 07:01:12 +0900 Subject: [PATCH 6/7] Update index page --- src/content/docs/specs/index.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/content/docs/specs/index.md b/src/content/docs/specs/index.md index 3d3aee0e..fbd7d6b5 100644 --- a/src/content/docs/specs/index.md +++ b/src/content/docs/specs/index.md @@ -8,10 +8,25 @@ sidebar: ## 1. Overview The Open Podcast API is a standard that facilitates the synchronization of podcast data between podcast clients. + This specification aims to provide comprehensive instructions for client and server developers looking to support the standard. ## 2. Definitions The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", and "MAY" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119). -## 3. Authentication \ No newline at end of file +### 2.1 Core and Optional endpoints + +To ensure that the end-user experience is consistent across implementations, the specifications mark endpoints and features as Core (required) and Optional. + +Core +: The feature or endpoint MUST be supported by all clients and servers. + +Optional +: The feature or endpoint is considered to be additional functionality. Clients and servers MAY optionally support any combination of these features. Any project implementing Optional functionality SHOULD inform users about what is supported. + + +## 3. Core endpoints + +- [Subscriptions](./subscriptions) + From 69a44a4da714b17229bc6800fb4833d8fef0ac35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20Ainsworth?= Date: Mon, 16 Mar 2026 07:03:27 +0900 Subject: [PATCH 7/7] Use absolute links --- src/content/docs/specs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/specs/index.md b/src/content/docs/specs/index.md index fbd7d6b5..f6c0a99b 100644 --- a/src/content/docs/specs/index.md +++ b/src/content/docs/specs/index.md @@ -28,5 +28,5 @@ Optional ## 3. Core endpoints -- [Subscriptions](./subscriptions) +- [Subscriptions](/specs/subscriptions)