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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions .github/workflows/generate-builder-feeds.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Generate builder feeds

on:
schedule:
# Daily at 01:15 UTC
- cron: '15 1 * * *'
workflow_dispatch:

permissions:
contents: write

concurrency:
group: generate-builder-feeds
cancel-in-progress: false

jobs:
generate:
name: generate · publish feeds
runs-on: ubuntu-latest
timeout-minutes: 20

steps:
- name: Checkout
uses: actions/checkout@v4
with:
# Use the triggering ref so workflow_dispatch from a feature branch
# can publish with that branch's generator/sources.
ref: ${{ github.sha }}
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm

- name: Install dependencies
run: npm ci

- name: Generate public feeds
run: npm run generate:builder-feeds

- name: Publish to feeds branch
run: |
set -euo pipefail
PUBLISH_DIR="${RUNNER_TEMP}/zero-tab-feeds"
rm -rf "$PUBLISH_DIR"
mkdir -p "$PUBLISH_DIR"

if git ls-remote --exit-code --heads origin feeds >/dev/null 2>&1; then
git fetch origin feeds
git worktree add "$PUBLISH_DIR" origin/feeds
git -C "$PUBLISH_DIR" checkout -B feeds
else
git worktree add --detach "$PUBLISH_DIR"
git -C "$PUBLISH_DIR" checkout --orphan feeds
git -C "$PUBLISH_DIR" rm -rf . >/dev/null 2>&1 || true
fi

cp builder-feeds/generated/feed-x.json "$PUBLISH_DIR/feed-x.json"
cp builder-feeds/generated/feed-blogs.json "$PUBLISH_DIR/feed-blogs.json"
cp builder-feeds/generated/feed-podcasts.json "$PUBLISH_DIR/feed-podcasts.json"
cp builder-feeds/generated/feed-videos.json "$PUBLISH_DIR/feed-videos.json"
cp builder-feeds/generated/generation-report.json "$PUBLISH_DIR/generation-report.json"

git -C "$PUBLISH_DIR" config user.name "github-actions[bot]"
git -C "$PUBLISH_DIR" config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git -C "$PUBLISH_DIR" add feed-x.json feed-blogs.json feed-podcasts.json feed-videos.json generation-report.json

if git -C "$PUBLISH_DIR" diff --cached --quiet; then
echo "No feed changes to publish."
else
git -C "$PUBLISH_DIR" commit -m "Update builder feeds $(date -u +%Y-%m-%dT%H:%M:%SZ)"
git -C "$PUBLISH_DIR" push origin HEAD:feeds
fi
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ dist/

# Personal config (landing page patterns, etc.) — never push to GitHub
extension/config.local.js

# Local feed generation output; published only on the feeds branch
builder-feeds/generated/
tests/fixtures/out/
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Before doing anything technical, tell the user what they're about to get:
> - **Save for later** bookmark individual tabs to a checklist before closing them
> - **Modular workspace cards** can be collapsed or hidden, with preferences stored locally
> - **Daily horoscope** generates a private, deterministic reading from the local date and selected zodiac sign
> - **AI Builder daily brief** opens as an independent full-height drawer with public updates from builders, podcasts, and engineering blogs
> - **AI Builder daily brief** opens as an independent full-height drawer with public updates from builders, podcasts, engineering blogs, and conference videos
> - **Local-first** tab and saved-item data never leaves the browser
>
> It's just a Chrome extension. Setup takes about 1 minute.
Expand Down Expand Up @@ -102,5 +102,5 @@ Once the extension is loaded:
- Run `npm run build` and load `dist/extension/` in Chrome.
- Saved tabs are stored in `chrome.storage.local` (persists across sessions).
- Tab management is fully local. Open-tab and saved-tab data is never uploaded.
- The optional AI Builder digest requests access only to `raw.githubusercontent.com`, fetches public feeds at most once per local day, and stores a compact cache locally.
- The optional AI Builder digest requests access only to `raw.githubusercontent.com`, fetches public feeds from this repository's `feeds` branch at most once per local day, and stores a compact cache locally.
- To update: `cd zero-tab && git pull && npm install && npm run build`, then reload the extension in `chrome://extensions`.
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ No server and no account are required. Open-tab URLs, titles, and saved links st
- Local, deterministic daily horoscope by zodiac sign
- Localhost port labels
- macOS-inspired light and dark themes
- Full-height AI Builder Daily Report drawer sourced from public Follow Builders feeds
- Full-height AI Builder Daily Report drawer sourced from public Zero Tab feeds
- Optional on-device translation through Chrome's built-in Translator API

## Install locally
Expand Down Expand Up @@ -46,7 +46,7 @@ No server and no account are required. Open-tab URLs, titles, and saved links st

Core tab management makes no external requests. Tab URLs, titles, Saved for later items, read state, and preferences are stored locally using Chrome extension storage.

AI Builder Daily Report is disabled until the user enables it. When enabled, Zero Tab requests optional access to `raw.githubusercontent.com` and downloads three public Follow Builders JSON feeds at most once per local calendar day. No tab, browsing, saved-link, identifier, or API-key data is included in those requests.
AI Builder Daily Report is disabled until the user enables it. When enabled, Zero Tab requests optional access to `raw.githubusercontent.com` and downloads three public JSON feeds published by this repository's `feeds` branch at most once per local calendar day. No tab, browsing, saved-link, identifier, or API-key data is included in those requests.

Translation uses Chrome's built-in on-device Translator API when available. The language model or language pack may be downloaded by Chrome, but report text is not sent to a third-party translation service by Zero Tab.

Expand Down Expand Up @@ -87,6 +87,16 @@ The previous Vanilla implementation remains in `extension/` as a migration
reference while the remaining publishing assets are moved to the new source
tree.

## Builder feed generation

Zero Tab publishes AI Builder JSON feeds from this repository's `feeds` branch.

```bash
npm run generate:builder-feeds
```

Sources live in [`builder-feeds/sources.json`](builder-feeds/sources.json). GitHub Actions runs [`.github/workflows/generate-builder-feeds.yml`](.github/workflows/generate-builder-feeds.yml) daily and on `workflow_dispatch`, then commits `feed-x.json`, `feed-blogs.json`, `feed-podcasts.json`, and `feed-videos.json` to the `feeds` branch root. The extension reads those files from `raw.githubusercontent.com`.

## Attribution and license

Zero Tab began as a fork of Zara Zhang's MIT-licensed original project.
Expand Down
9 changes: 9 additions & 0 deletions builder-feeds/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Builder feeds

Curated source list and local generation output for the AI Builder Daily Report.

- [`sources.json`](sources.json) — X handles, blogs (RSS and/or HTML), podcasts (RSS), conference videos (YouTube RSS)
- `generated/` — local output of `npm run generate:builder-feeds` (gitignored)
- Published artifacts live on the repository `feeds` branch root

Generation is owned by Zero Tab. It does not call Follow Builders services or APIs.
70 changes: 70 additions & 0 deletions builder-feeds/sources.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"version": 1,
"x": [
{ "name": "Boris Cherny", "handle": "bcherny" },
{ "name": "Anthropic", "handle": "AnthropicAI" },
{ "name": "Guillermo Rauch", "handle": "rauchg" },
{ "name": "Simon Willison", "handle": "simonw" },
{ "name": "Addy Osmani", "handle": "addyosmani" },
{ "name": "Swyx", "handle": "swyx" },
{ "name": "Andrej Karpathy", "handle": "karpathy" }
],
"blogs": [
{
"name": "Anthropic Engineering",
"url": "https://www.anthropic.com/engineering"
},
{
"name": "Anthropic News",
"url": "https://www.anthropic.com/news"
},
{
"name": "OpenAI Blog",
"url": "https://openai.com/blog",
"rssUrl": "https://openai.com/blog/rss.xml"
},
{
"name": "Simon Willison",
"url": "https://simonwillison.net/",
"rssUrl": "https://simonwillison.net/atom/everything/"
},
{
"name": "Julia Evans",
"url": "https://jvns.ca/",
"rssUrl": "https://jvns.ca/atom.xml"
},
{
"name": "Latent Space",
"url": "https://www.latent.space/",
"rssUrl": "https://www.latent.space/feed"
},
{
"name": "Cursor Blog",
"url": "https://cursor.com/blog"
}
],
"podcasts": [
{
"name": "Latent Space",
"rssUrl": "https://api.substack.com/feed/podcast/1084089.rss"
},
{
"name": "Practical AI",
"rssUrl": "https://changelog.com/practicalai/feed"
}
],
"videos": [
{
"name": "AI Engineer",
"rssUrl": "https://www.youtube.com/feeds/videos.xml?channel_id=UCLKPca3kwwd-B59HNr-_lvA"
},
{
"name": "Cursor Compile",
"rssUrl": "https://www.youtube.com/feeds/videos.xml?playlist_id=PLuI2ZfvGpzwDCn0njJpjZ3ZiEpCqhN7BK"
},
{
"name": "Figma Config",
"rssUrl": "https://www.youtube.com/feeds/videos.xml?playlist_id=PLXDU_eVOJTx6erPKfFHtCNbyCmcCn4zrp"
}
]
}
2 changes: 1 addition & 1 deletion docs/privacy.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ <h2>Local storage</h2>
<p>Saved links, preferences, AI Builder Daily Report cache, read state, and translations are stored in <code>chrome.storage.local</code>. Zero Tab does not operate a server and does not synchronize this data to the publisher.</p>

<h2>AI Builder Daily Report</h2>
<p>This optional feature is disabled until the user enables it. When enabled, Zero Tab downloads public JSON feeds from <code>https://raw.githubusercontent.com/</code>. These requests do not contain tab data, saved links, browsing history, identifiers, credentials, or API keys.</p>
<p>This optional feature is disabled until the user enables it. When enabled, Zero Tab downloads public JSON feeds from <code>https://raw.githubusercontent.com/beforeload/zero-tab/feeds/</code>. These requests do not contain tab data, saved links, browsing history, identifiers, credentials, or API keys.</p>

<h2>On-device translation</h2>
<p>When supported and explicitly requested, Zero Tab uses Chrome's built-in Translator API. Chrome may download a language pack or model. Zero Tab does not send report text to a third-party translation API.</p>
Expand Down
3 changes: 2 additions & 1 deletion extension/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -1313,6 +1313,7 @@ function formatDigestDate(dateStr) {
function digestKindLabel(kind) {
if (kind === 'blog') return 'Blog';
if (kind === 'podcast') return 'Podcast';
if (kind === 'video') return 'Video';
return 'X';
}

Expand Down Expand Up @@ -1372,7 +1373,7 @@ function renderDigestItemCard(item, state, stale, targetLanguage) {
const safeExcerpt = escapeHtml(localized?.excerpt || item.excerpt);
const safeSource = escapeHtml(item.source);
const safeItemId = escapeHtml(item.id);
const kind = ['x', 'blog', 'podcast'].includes(item.kind) ? item.kind : 'x';
const kind = ['x', 'blog', 'podcast', 'video'].includes(item.kind) ? item.kind : 'x';
const dateLabel = escapeHtml(formatDigestDate(item.publishedAt));
const isRead = state.readIds?.includes(item.id);

Expand Down
Loading
Loading