diff --git a/domain-skills/dev-to/publish.md b/domain-skills/dev-to/publish.md
new file mode 100644
index 00000000..d1465c35
--- /dev/null
+++ b/domain-skills/dev-to/publish.md
@@ -0,0 +1,55 @@
+# dev-to/publish — publish an article to DEV.to via the markdown editor (battle-tested 2026-06-28)
+
+Driving `dev.to/new` with browser-harness against the user's logged-in Chrome. DEV.to gives an **instant, no-queue, high-DR dofollow backlink** in the article body — ideal for cross-posting an existing technical article. Far simpler than rich editors: it's one markdown textarea with Jekyll front matter.
+
+> Companion skill: `dev-to/scraping.md` (reading dev.to). This file covers writing/publishing.
+
+## Pre-flight
+- User logged into dev.to in Chrome. `new_tab("https://dev.to/new")` → if the editor (`textarea#article_body_markdown`) is present you're in; if you see a login wall, pause and ask.
+- The "basic markdown editor" puts **everything in one textarea** — title/tags/description go in YAML front matter at the top, body below.
+
+## The flow
+
+### 1. Build the content (front matter + markdown body)
+```
+---
+title: "
"
+published: true # true = publish now; false = save as draft
+description: "<≤ ~150 chars — quote it too (colons/# are common in descriptions)>"
+tags: programming, ai, webdev, saas # comma-separated, MAX 4, lowercase, no spaces/hyphens inside a tag
+canonical_url: ""
+---
+
+
+```
+Quote any user-supplied scalar (title/description/canonical_url). dev.to's front matter is YAML, so an unquoted value containing `:` or a leading `#` will fail the parse or be misread, and the publish silently breaks.
+- **YouTube / Tweet embeds:** liquid tag on its own line — `{% embed https://youtu.be/ %}`.
+- Body links are normal markdown `[text](https://...)` and render **dofollow**.
+
+### 2. Insert it (React-safe native setter — don't type)
+The editor is one big controlled textarea. Set the value via the native setter + fire input/change (typing char-by-char is slow and flaky):
+```python
+import json
+md = open("/path/post.md").read()
+js("""(function(){
+ var t=document.querySelector('textarea#article_body_markdown')||
+ [].slice.call(document.querySelectorAll('textarea')).sort((a,b)=>b.getBoundingClientRect().height-a.getBoundingClientRect().height)[0];
+ var setter=Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype,'value').set;
+ setter.call(t, %s); t.dispatchEvent(new Event('input',{bubbles:true})); t.dispatchEvent(new Event('change',{bubbles:true}));
+ return 'set:'+t.value.length;
+})()""" % json.dumps(md))
+```
+
+### 3. Publish
+- Scroll to the bottom; the button is **"Save changes"** (there is no separate "Publish" button in this editor). With `published: true` in the front matter, clicking **Save changes publishes**; with `published: false` it saves a draft.
+- Success = the URL changes to `https://dev.to//`.
+
+## Gotchas (field-tested)
+- **"Invalid authenticity token" (CSRF) on save** — the #1 failure. The Rails CSRF token goes stale if the page sat open a while (or across a failed submit). **Fix: `goto("https://dev.to/new")` to reload (fresh token), re-insert the content, save again.** Don't dwell between loading the editor and submitting.
+- **No "Publish" button** — it's "Save changes"; the `published:` front-matter flag decides draft vs live. Don't hunt for a Publish button.
+- **Tags:** max 4, lowercase, each a single token (e.g. `webdev`, not `web-dev`); invalid tags can block the save.
+- **Cover image** is optional but boosts the home-feed; drag-drop only (skip in automation, the user can add later).
+- Cross-posting the same article to multiple sites (HackerNoon, Hashnode, dev.to): set `canonical_url` to the original on the secondary copies if you care about duplicate-content; for pure-backlink plays it's optional.
+
+## Why it's worth it
+Instant publish, no moderator queue, DEV.to is high domain authority, and body links are dofollow — so a single cross-post of an existing article = one of the "5 backlinks that matter," in ~3 minutes. Hashnode works the same way (markdown editor + instant publish).
diff --git a/domain-skills/google-search-console/check.md b/domain-skills/google-search-console/check.md
new file mode 100644
index 00000000..43d6a1d6
--- /dev/null
+++ b/domain-skills/google-search-console/check.md
@@ -0,0 +1,42 @@
+# google-search-console/check — inspect indexing & submit sitemaps (battle-tested 2026-06-29)
+
+Driving `search.google.com/search-console` with browser-harness against the user's logged-in Google account. **No API here** unless service-account/OAuth is set up — this is the browser path. GSC is a heavy Angular SPA: use coordinate clicks + `Input.insertText`, and **read reports via screenshots** (text extraction is sparse).
+
+## Property URL
+Everything is scoped by `resource_id`. For a **domain property** it's `sc-domain:` (e.g. `sc-domain:tryskilly.app`). Deep-link directly:
+- Overview: `…/search-console?resource_id=sc-domain:`
+- Sitemaps: `…/search-console/sitemaps?resource_id=sc-domain:`
+- Pages (indexing): `…/search-console/index?resource_id=sc-domain:`
+- Performance: `…/search-console/performance/search_analytics?resource_id=sc-domain:`
+
+Confirm login + property by reading the top-left property name and the left nav (Overview / Performance / URL inspection / Pages / Sitemaps…). If it redirects to a Google login, pause and ask the user.
+
+## The 4 checks (priority order)
+
+### 1. Submit / re-submit the sitemap (highest value after a deploy)
+- Go to the Sitemaps page. The "Add a new sitemap" card has an input + **SUBMIT** button.
+- **GOTCHA — domain properties require the FULL URL.** Entering a relative path (`sitemap-index.xml`) returns **"Invalid sitemap address"**. Enter `https:///sitemap-index.xml`.
+- **GOTCHA — two inputs on the page.** The top header has an "Inspect any URL" bar AND the card has the sitemap input — both are ``. `document.querySelector('input')` grabs the **wrong** (top) one. Target the sitemap card's input (it sits lower in the main content, ~y 239 at 1080p), then `click` it → `Input.insertText` the full URL → click SUBMIT.
+- Success = a green **"Sitemap submitted successfully"** dialog; the submitted-sitemaps table shows the row with Status **Success**, Last read, and **Discovered pages**.
+- Re-submitting after adding pages nudges a fresh read (Google also re-reads on its own schedule).
+
+### 2. Pages (indexing) report — the real "is everything indexed?" answer
+- Two tiles: **Indexed** (count) and **Not indexed (N reasons)**.
+- Scroll to **"Why pages aren't indexed"** for the per-reason table.
+- **GOTCHA — data lags ~2 weeks** ("Last update" date at top). Pages deployed today won't show here for a while; the sitemap submit (step 1) is what accelerates discovery.
+
+### 3. URL inspection
+- Top "Inspect any URL" bar → type a full URL → Enter. Shows whether it's on Google + why. Click **"Request indexing"** to push a priority page into the queue (rate-limited, ~10–20/day).
+
+### 4. Performance
+- Impressions / clicks / avg position / top queries. Early-stage sites show little — that's expected.
+
+## Interpreting "not indexed" reasons
+- **"Discovered – currently not indexed"** → Google found the URL (via sitemap/links) but deprioritized crawling it. This is a **domain-authority / crawl-budget** signal, NOT a technical error. Fix: earn backlinks, strengthen internal linking (hub→spoke), Request-Indexing the key pages, and wait. If most pages sit here, **do not mass-generate more pages** — they'll pile up unindexed; raise authority first.
+- **"Crawled – currently not indexed"** → crawled but judged low-value/thin/duplicate. Improve content quality/uniqueness.
+- **"Page with redirect"** → usually benign (trailing-slash or http→https canonicalization); the redirect target is the indexed URL.
+- **"Duplicate without user-selected canonical" / "Alternate page with canonical"** → set/confirm canonical tags.
+- **"Not found (404)"** → a referenced URL is broken; find + fix or remove the reference.
+
+## Why no API
+GSC has a Search Console API (sitemaps, URL inspection, search analytics) but it needs a **service account added to the property** or an **OAuth client** — neither is set up here (a prior attempt stalled on service-account propagation). Until that's configured, this browser flow is the way. If you do set it up, the `searchconsole.googleapis.com` URL-inspection + `webmasters` sitemaps endpoints replace steps 1–3.
diff --git a/domain-skills/youtube/upload.md b/domain-skills/youtube/upload.md
new file mode 100644
index 00000000..2a98ca7d
--- /dev/null
+++ b/domain-skills/youtube/upload.md
@@ -0,0 +1,51 @@
+# youtube/upload — publish a video to YouTube via Studio (battle-tested 2026-06-28)
+
+Driving `studio.youtube.com` with browser-harness against the user's logged-in Chrome. This is the durable map of the upload flow — follow it and the first try works. The whole flow is ~5 steps but several controls resist JS selectors and need **coordinate clicks** (noted below).
+
+## Pre-flight
+- User must be logged into the target channel in Chrome. Confirm: `page_info()` on `studio.youtube.com` returns `.../channel/` and title contains "YouTube Studio".
+- **Check for an existing video first (match by title, not position).** Go to `.../videos/upload` (the full Content list, not just the dashboard's "Latest video" card — that card only shows the most recent one and will miss older duplicates). Scrape each row's title cell **and** its `a[href*="/video/"]` → `/video/()/` together, then find the row whose title matches the video you intend to publish; build `https://youtu.be/` from that row's id. Never assume the first/most-recent row is the match — confirm the title, or you'll reuse (or skip over) the wrong video.
+- Have the absolute path to the local `.mp4` ready.
+
+## The flow
+
+### 1. Open the upload dialog
+- Click **Create** (top-right): match `aria-label === "Create"` (an `