Commit e50c31b
feat(website): AI search optimization — Search Console harness, structured data, and measurement (#826)
* docs(plan): ai search optimization plan for threadplane.ai
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(website): search console api service-account auth
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(website): typed search console api wrappers
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(website): cache search console access tokens
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(website): search console snapshot puller
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(website): keep partial inspection results and fail loudly on a bad sitemap
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(website): search console analysis report
Pure analysis helpers (striking distance, zero-impression pages,
unindexed, canonical mismatches, weak CTR) plus a markdown report
generator over the .gsc snapshots.
The report reads inspection-errors.json when present so a partial
inspection sweep is stated as partial: failed URLs stay in the sitemap
inventory, the failure count is reported, and the index-health counts
are labelled lower bounds rather than implying a clean bill of health.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(website): cover analysis boundaries and harden the gsc report
Tests: real ordering assertions for findStrikingDistance and findWeakCtr
(the previous one-row fixture passed with .sort() deleted), table-driven
filter boundaries including the inclusive/exclusive asymmetry between
impressions and ctr, and first coverage for findCanonicalMismatches and
findWeakCtr. The both-canonicals-required policy is now pinned by test
and stated in the doc comment.
Report: InspectionFailure moves to api.ts so the pull/report
serialization contract is declared once; snapshot reads report a missing
or corrupt file by name and point at the pull instead of throwing a raw
ENOENT, with readOptional keeping its own existence check so genuine
absence stays distinguishable; URL comparison normalizes protocol, host
case, www, fragment and query string so a tagged URL is no longer
reported as a page with zero impressions; all four bullet lists are
capped, not just the failures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(plan): correct website test command (no nx test target exists)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(website): fix stale positioning proof-point assertion
* feat(website): emit honest lastmod in sitemap
Google ignores changefreq/priority and uses lastmod when it is honest, so
the sitemap now emits only lastmod, derived per route from its real source:
blog frontmatter dates, docs .mdx files, and page.tsx templates (plus the
solutions data module for the programmatic /solutions/* pages).
Times come from git commit history rather than file mtimes: a fresh CI
checkout rewrites every mtime to clone time, which would claim the whole
site changed on every deploy. Shallow clones are detected and their grafted
boundary commits discarded, and any route we cannot date honestly simply
omits lastmod rather than fabricating one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(website): detect shallow clones through the common git dir
The `shallow` marker lives in the common git dir, not the per-worktree
gitdir, so `--absolute-git-dir` misses it inside a linked worktree
(.git/worktrees/<name> vs .git) and the sitemap silently dropped lastmod
for every file-derived route.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor(website): extract sitemap dates and close date-fabrication paths
Moves the sitemap date logic out of site-metadata.ts into sitemap-dates.ts
and removes the remaining ways a date could be invented:
- Delete the file-mtime fallback. "Not shallow" never implied "committed" —
a fresh full clone also rewrites every mtime to checkout time, so a single
lookup miss could publish a build-time lastmod.
- Run git with core.quotePath=false (octal-escaped non-ASCII paths would
never match a lookup, silently falling through) and log.showSignature=false
(a user's config could interleave gpg: lines into the file list).
- Bound the subprocess with timeout/SIGKILL so a stalled git cannot hang the
build; a kill throws and degrades like any other git failure.
- Require a well-formed "<epoch> <sha1>" remainder before treating a line as
a commit header, and take paths verbatim so leading whitespace survives.
Blog routes now take the later of the frontmatter date and their .mdx commit
time, since lastmod means last modified rather than published.
Extracts parseGitLog as a pure function and unit-tests the degradation the
design rests on — most importantly that a grafted shallow-boundary commit
yields no entry rather than a clone-time one — plus a test pinning the
absence of changeFrequency/priority. Integration tests now tolerate a
history-less checkout instead of asserting completeness unconditionally.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(website): article metadata + canonical brand spelling
Emit article:published_time / modified_time / author / tag on blog posts,
with modified_time derived from real git commit times (sitemap-dates)
rather than defaulting to the publish date. createPageMetadata gains an
optional per-page social image for the Task 9 OG-image routes.
Unify the brand on "Threadplane" (was "ThreadPlane" in blog titles and
prose) and the docs title separator on an em dash.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor(website): single-source the blog modified time
Drop the redundant `lastModified > published` guard in the blog route: it
could never change the value (getRouteLastModified already returns the
max) and its only effect was flipping modifiedTime to undefined, which
made unedited posts emit a date-only article:published_time while edited
ones emitted a full ISO timestamp. The `?? publishedTime` fallback in
createPageMetadata is now the one place that rule lives, and both fields
are ISO timestamps on every post.
Date parsing moves to the shared `publishedDate()` helper, so a malformed
frontmatter date drops the article block instead of shipping NaN-adjacent
garbage as article:published_time.
getRouteLastModified's post index is now a required `postsByRoute`
(renamed: the keys are route paths) rather than an optional argument with
a drafts policy that diverged from the caller's, which could return
different answers for the same route. Callers build it with the new
getPostsByRoute() or pass the post they already hold.
Replaces the tautological SITE_NAME assertion with a real scan of src,
content, scripts, and e2e for the mis-cased brand, and covers the
modifiedTime fallback plus the absence of article keys on non-article
pages.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(website): schema.org json-ld builders
Pure builders for Organization, WebSite, SoftwareSourceCode, BlogPosting,
TechArticle, and BreadcrumbList nodes, plus a JsonLd render component. Not
mounted on any route yet.
Every emitted URL was verified to resolve: the repository is
cacheplane/angular-agent-framework (not blove/...), sameAs links the public
package page rather than the member-gated npm org page, and the author URL
and per-post OG image are omitted until /about and the fixed image route
exist.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(website): make json-ld round-trip assertions real
expectSerializable claimed a round-trip but never compared the parsed result
to the input, so JSON.parse(JSON.stringify(obj)) could not fail and the six
"serializes to JSON" tests were vacuous. Adding toStrictEqual immediately
caught blogPostingJsonLd leaving an undefined-valued `keywords` key when a
post has no tags, where techArticleJsonLd already used the omit pattern for
dateModified; both builders now agree.
Also adds table-driven coverage of @context and the Organization @id
reference across every builder that carries one, a rootJsonLd() @graph that
makes the three root-layout nodes physically inseparable so an @id reference
cannot be orphaned by mounting a subset, and an exported BreadcrumbCrumb type.
Drops Organization.logo: a 1200x630 marketing social card is not a brand
mark, and no square mark exists in the repo. Omitting is honest; the property
should be restored once a real mark ships.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(website): mount json-ld on layout, blog, and docs
Root layout mounts `rootJsonLd()` as a single `@graph`, so the Organization
node travels with every page and the `@id` references made by BlogPosting and
TechArticle always resolve.
Blog posts emit BlogPosting + BreadcrumbList; docs pages emit TechArticle +
BreadcrumbList. Both reuse the exact sources their `generateMetadata` already
uses, so `dateModified`, `og:modified_time`, and the sitemap `<lastmod>` agree:
`getPostLastModified()` factors the single-post date derivation the blog route
had inline, and `resolveDocDescription()` exposes the description accessor that
`getDocMetadata()` builds the meta description from.
The docs breadcrumb links its library rung at that library's introduction page,
mirroring the visible <DocsBreadcrumb>; `/docs/<library>` has no route and would
have been a crumb pointing at a 404.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(website): pin breadcrumb and description invariants at their real surfaces
The docs-description test was a tautology: this branch made `getDocMetadata`
call `resolveDocDescription`, so both sides of the comparison had become the
same function and 116 double MDX reads asserted nothing. Replaced with a spec
that renders the docs route component and compares the JSON-LD it actually
emits against the route's own `generateMetadata` — the surface where divergence
can really occur.
`libraryIntroPath()` now lives in docs-config and is called by both the visible
<DocsBreadcrumb> and the page's BreadcrumbList, so the "markup mirrors the
visible trail" claim is structural instead of commented. The new spec asserts
the JSON-LD library rung equals the href the rendered component produces, and
that every non-final rung is a route the sitemap knows.
Also: the sitemap-agreement test no longer passes on two undefineds; the
"unmodified" fallback rule collapses into one `resolveModifiedTime()` shared by
og and JSON-LD; the docs fallback description and the docs last-modified
derivation each get one home; and both page mounts hoist their builder calls
above the return.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(website): stop the per-post OpenGraph image route 500ing
Every blog post's social card returned HTTP 500 in production. Satori
rejects a div with more than one child node unless it carries an explicit
`display`, and the byline rendered three children (author, separator,
date) with none. The root `/opengraph-image` route was unaffected only
because it is prerendered at build time, while `/blog/[slug]/opengraph-image`
is server-rendered on demand.
Separately, the route read the bundled Garamond TTF through a parent
traversal (`join(here, '../../EBGaramond-Bold.ttf')`) that Next's file
tracer cannot statically resolve, so the font was absent from the
deployed function's trace — confirmed by diffing the two routes'
`route.js.nft.json`. Move font loading into `src/app/og-font.ts`,
colocated with the TTF, so the sibling-filename form traces for every
caller. Both routes now share it.
Make the failure mode safe: `satoriFonts` returns `undefined` rather than
`[]` when every font fails, since Satori throws on an empty list but
falls back to its bundled Noto Sans when the option is omitted. A plain
card beats a 500.
With the route verified locally, point `og:image`/`twitter:image` and the
BlogPosting `image` at the per-post card, flipping the two TODOs that
deliberately waited on this fix.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* perf(website): prerender per-post og cards
The per-post card route was `ƒ` (server-rendered on demand), so Satori
markup errors could only surface as a production 500 — which is how the
missing `display: flex` on the byline shipped and broke all nine cards.
The root route escaped the same class of bug purely because it is
prerendered, where such an error fails the build instead.
Add `generateStaticParams`, mirroring this segment's `page.tsx`, so every
published post's card is generated at build time. The route now reports
as `●` and emits nine 1200x630 PNGs into the build output. This turns
render-time Satori failures into build failures, drops two uncached
Google Fonts round-trips and an MDX read per request on a path crawlers
hit, and moves `resolveWebsiteDir()` onto the build's cwd — which is
known to resolve — instead of an unverified serverless cwd.
Also:
- Extract `loadCardFonts` so both routes stop hand-assembling the same
font descriptors, and widen `OgFont.weight` to the CSS weight domain
so call sites no longer need `as const`.
- Add a shared `ogImagePath(slug)` used by both `blog/[slug]/page.tsx`
and `blogPostingJsonLd`; the two built the same URL independently and
could drift with both suites green.
- Drop the two defensive `display: flex` values on single-child divs and
scope the comment to the byline, which is the one that was diagnosed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(website): keep anchor glyphs out of heading text
Docs and blog headings rendered a literal `#` text node before the
heading children, so every extracted heading came out as
`#Prerequisites` / `#1. Install the packages` — polluting search
snippets, page outlines, and anything summarizing the page from the DOM.
The `#` is now CSS generated content on `.heading-anchor::before`, and
the anchor is rendered after `{children}`. Extracted text is exactly the
heading text, while the permalink stays a real link with its
`aria-label` and its place in the tab order.
Both MDX heading overrides (MdxRenderer and the choosing-an-adapter
page, which had its own copy of the same bug) now share one
`mdxHeadingComponents` module, covered by a regression spec asserting
heading `textContent` carries no `#`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(website): guard the css-generated heading anchor
The heading `#` glyph is now CSS generated content, so the visible
permalink affordance hangs entirely on one declaration that jsdom
cannot see — it does not resolve pseudo-element content. Add a
Playwright assertion in the docs spec that reads
`getComputedStyle(el, '::before').content` off the rendered anchor and
pins it to `"#"`, alongside a check that the heading text carries no
glyph. Verified red by commenting out the rule (received "none").
Also label the permalink with the heading text rather than the slug, so
it announces "Link to At a glance" instead of "Link to at-a-glance".
Children are frequently nested nodes, so the text is flattened
recursively and falls back to the id when nothing can be derived.
Correct the module comment to name the real selectors: the CSS is scoped
to H2/H3 inside `.docs-prose`, not to `.heading-anchor` generally.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(website): add /about page carrying a Person entity
Establishes an attributable author for the site: an AboutPage JSON-LD
graph whose mainEntity is a Person built from the existing
`blogAuthors['brian']` record, referencing the Organization the root
layout already mounts.
Now that the route exists, blog bylines carry `author.url` pointing at
it — deliberately omitted before, when a 404 author URL was the worse
signal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(website): unify the author entity across blog and about
Every BlogPosting byline now carries the same `@id` the /about Person
declares, so the two are one entity to a consumer rather than two nodes
that merely share a name — which is the point of the attribution work.
`knowsAbout` moves onto the Author record beside `bio`: it is a claim
about the person, so it belongs with the person rather than with one
page that renders them.
Also: /about reads the author through `getAuthor()` for its missing-key
fallback, and shares the repository URL constant instead of adding a
third copy.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(blog): question-form section headings in the 2026-05/06 posts
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(website): record the no-scaled-content rule for solutions pages
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(website): track ai crawler and ai referral traffic
Google Search Console's Generative AI report is UI-only, and AI crawlers
never execute JavaScript, so the client PostHog snippet cannot observe
either signal. Add edge middleware that classifies the request and emits
marketing:ai_crawler_visit / marketing:ai_referral_visit.
- ai-traffic.ts: pure classifiers. Crawler UAs are matched on published
tokens (plain Googlebot/Applebot deliberately excluded); referrers are
matched on the parsed hostname exactly or as a subdomain, so lookalikes
such as evil-chatgpt.com.attacker.net do not classify.
- Capture goes direct to the PostHog ingest host over fetch, not through
posthog-node (a Node library; middleware runs on the Edge runtime) and
not through the /ingest/* rewrites, which exist for the browser.
- Registered with FetchEvent#waitUntil so the response is never delayed,
with a 2s abort: a dropped event is acceptable, a hung request is not.
- Anonymous by construction: $process_person_profile false, $ip null,
pathname only (never the query string), and no UA on referral events.
- Crawler events are deduped per crawler/path/hour, best-effort and
per-instance, so a crawler looping one URL cannot become a firehose.
- Matcher excludes api, _next, ingest, og/twitter image routes and any
path whose last segment has an extension.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(website): bound ai-traffic event volume and cover the capture payload
Both inputs to this feature are attacker-controlled, and the per-key dedup
was not an abuse ceiling: varying the path defeats it entirely, and the
referral path had no limiter at all. Demonstrated live — 700 requests with
a spoofed chatgpt.com Referer produced 700 events. Anyone reading the
bundle could bill the PostHog account arbitrarily or poison the dataset
this feature exists to produce.
- Add a per-instance token bucket on TOTAL emissions (500/hour, burst 500),
so the blast radius is bounded regardless of key variety. Chosen well
above honest volume: 141 sitemap URLs, and a simultaneous full-site sweep
by three crawlers landing on one instance is ~423. Per-instance and
best-effort like the dedup: it bounds blast radius, it is not a global
quota.
- Cover sendToPostHog, where every privacy invariant lives. Asserts the
exact wire payload for both event types: $process_person_profile false,
$ip null, no query string, no referrer URL, no UA on referral events,
and that a caller cannot override the anonymous properties.
- Observe /llms.txt, /llms-full.txt, /sitemap.xml and /robots.txt. These
are the file written for AI consumers and the strongest crawler-intent
signals that exist; losing them to the blunt extension rule undercut the
feature's own premise.
- Anchor the og/twitter-image exclusion to a segment boundary, so a page
named /my-opengraph-image-guide is no longer silently dropped.
- Rename middleware.ts to proxy.ts (Next 16 deprecates the middleware file
convention). NextProxy is NextMiddleware, so waitUntil is unchanged. This
moves the runtime from edge to nodejs, re-verified below.
- Dedup key is now JSON.stringify([bucket, crawler, path]): a path may
legitimately contain any delimiter, and a collision silently drops an
event. Drop keepalive (a browser-unload primitive; waitUntil is what
holds the invocation). Drop the unreachable '[::1]' branch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* revert(website): keep ai-traffic middleware on the edge runtime
Reverts only the proxy.ts rename from 3413824. Every other change in that
commit — the emission token bucket, the sendToPostHog payload coverage, the
llms.txt/sitemap.xml/robots.txt matchers, the anchored og-image exclusion —
is kept.
The rename silenced a cosmetic deprecation warning at the cost of moving the
runtime from Edge to Node on every request to a public production site.
Next 16's proxy convention always runs on Node, so `git mv` was not a file
rename but a deployment-shape change: different cold-start and pricing
characteristics on Vercel, and unverifiable from a local build. The
deprecation is a warning, not a break, and 16.1.7 builds and runs the
middleware convention fine.
The migration is still worth doing — it additionally makes process.env
runtime-read instead of build-inlined, so token rotation would stop needing
a redeploy — but it belongs in its own change with a preview deploy behind
it, not bundled in as a side effect of a warning fix. A comment on the file
records that the warning is deliberate and that the migration is mechanical,
because the code uses only Web-standard APIs.
Re-verified on Edge after the revert, rather than assumed to carry over from
the Node run: chunks back under server/edge/ with all five matchers; a
5s-hanging PostHog left /contact, /llms.txt and /pricing returning 200 in
9-69ms with all three events still delivered; two 700-request floods
(spoofed chatgpt.com Referer, and GPTBot across 700 distinct paths) each
capped at 501 events; llms.txt, llms-full.txt, sitemap.xml and robots.txt
all captured; ordinary traffic, Googlebot, a google.com referrer and the
evil-chatgpt.com.attacker.net lookalike all emitted nothing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(blog): architecture diagrams for the tutorial posts
Every blog post shipped zero images. Add three hand-authored SVG
diagrams, one per architecture-heavy post, each drawn from what the
post actually explains:
- ag-ui-event-flow: the client-tool round trip — the browser ships its
tool catalog up, the server streams AG-UI events down, and the tool
result the browser produces starts the next run.
- langgraph-threads-and-runs: the ACTIVE_THREAD signal as the pivot
between the thread list and the active run.
- agent-contract-boundary: two runtimes reduced into one neutral
contract of Angular signals, with user intent travelling back.
Each SVG paints its own surface and carries a <title>/<desc>, so it
reads on a light or dark page without depending on the host theme.
Colors come from the design tokens' light palette.
Add an `img` override to MdxRenderer that emits explicit width/height
(so the box is reserved before the file loads, keeping layout shift at
zero) plus lazy loading and async decoding.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(gtm): ai search measurement runbook
The GSC harness README and every generated report already point at this
file. Write it.
Covers what each source can and cannot answer, the monthly routine over
gsc:pull / gsc:report, the AI crawler and referral events emitted from
Edge middleware, and the baseline from the first real pull
(2026-05-19 → 2026-08-17) so later pulls have something to compare to.
Two things it is deliberately blunt about:
- The Search Console Generative AI performance report is UI-only. It is
not in searchanalytics.query, not a searchAppearance value, and not in
the BigQuery export. The only way to read it is by hand, so the doc
carries the manual procedure and a running log to paste it into.
- Query-dimension totals (528 impressions) are lower than page-dimension
totals because Google anonymizes rare queries. Page-level is the true
volume.
Plus a do-not-do list — llms.txt as a Search tactic, content chunking,
AI-specific keyword rewrites, inauthentic mentions — so nobody re-adds
them. We keep /llms.txt because some non-Google assistants read it, not
because it helps Google.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(blog): size diagrams for the real prose column
The diagrams were authored at 880 wide. `.docs-prose` computes to
706.56px — `max-width: 70ch` wins over Tailwind's `max-w-none` because
it sits later in the built stylesheet — so they rendered at scale 0.803
and put 12.5px labels at 10.0px. On a 375px viewport the column is 323px,
scale 0.372, detail text at 4.6px. Unreadable, and never measured.
Re-author all three at a 700 viewBox so they render 1:1 in the column,
with the type scale raised a step (meta 13px, eyebrow 11.5px). The wider
layouts do not survive 700px, so each one goes more vertical:
- ag-ui-event-flow: one column of six steps, browser/server marked by a
tinted left rule and a corner tag, with the return rail on the left.
- langgraph-threads-and-runs: two rows pivoting on ACTIVE_THREAD, edge
labels moved above each exchange instead of into the column gap.
- agent-contract-boundary: four stacked bands instead of four columns.
Below 706px the paragraph scrolls rather than the figure shrinking —
the same treatment `.docs-table-scroll` gives a wide table — so labels
never scale below their authored size. Verified: the page itself does
not gain a horizontal scrollbar at 371px.
Also drop the inline `style` from the img override in favour of a
`.docs-diagram` class. It duplicated `.docs-prose > p > img`, silently
overrode that rule's `margin: 2rem auto`, and being inline would have
clobbered any author-supplied `style` on a future markdown image.
Per-diagram intrinsic sizes replace the single shared constant now that
the three differ in height.
Measured effective font size of the smallest text class, in the built
page: 11.5px at 1280px wide and 11.5px at 375px (scale 1.0 at both).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(gtm): scope the dedup claim to crawler events
The bullet sat under a heading covering both AI events, but
`shouldEmitCrawlerEvent` is only called on the crawler path in
middleware.ts. The 500/hour token bucket is the one bound both share.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore: ignore local service-account keys directory
Keeps keys/gcp.json (Search Console service account) out of git.
Also added to .git/info/exclude so it takes effect in every worktree
immediately, not only after this branch merges.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>1 parent 13d8a87 commit e50c31b
68 files changed
Lines changed: 6592 additions & 183 deletions
File tree
- apps/website
- content/blog
- e2e
- public/blog/diagrams
- scripts/gsc
- src
- app
- about
- blog
- [slug]
- docs
- [library]/[section]/[slug]
- choosing-an-adapter
- components
- docs
- mdx
- lib
- analytics
- docs
- gtm
- superpowers/plans
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
67 | 67 | | |
68 | 68 | | |
69 | 69 | | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
Lines changed: 2 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | | - | |
| 20 | + | |
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| |||
160 | 160 | | |
161 | 161 | | |
162 | 162 | | |
163 | | - | |
| 163 | + | |
164 | 164 | | |
165 | 165 | | |
166 | 166 | | |
| |||
Lines changed: 5 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
57 | 57 | | |
58 | 58 | | |
59 | 59 | | |
60 | | - | |
| 60 | + | |
61 | 61 | | |
62 | 62 | | |
63 | 63 | | |
| |||
152 | 152 | | |
153 | 153 | | |
154 | 154 | | |
155 | | - | |
| 155 | + | |
156 | 156 | | |
157 | 157 | | |
158 | 158 | | |
| |||
164 | 164 | | |
165 | 165 | | |
166 | 166 | | |
167 | | - | |
| 167 | + | |
168 | 168 | | |
169 | 169 | | |
170 | 170 | | |
| |||
267 | 267 | | |
268 | 268 | | |
269 | 269 | | |
270 | | - | |
| 270 | + | |
271 | 271 | | |
272 | 272 | | |
273 | 273 | | |
| |||
300 | 300 | | |
301 | 301 | | |
302 | 302 | | |
303 | | - | |
| 303 | + | |
304 | 304 | | |
305 | 305 | | |
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
33 | | - | |
| 33 | + | |
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
| |||
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | | - | |
| 51 | + | |
52 | 52 | | |
53 | 53 | | |
54 | 54 | | |
| |||
Lines changed: 2 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
| 81 | + | |
| 82 | + | |
81 | 83 | | |
82 | 84 | | |
83 | 85 | | |
| |||
Lines changed: 2 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
| 50 | + | |
| 51 | + | |
50 | 52 | | |
51 | 53 | | |
52 | 54 | | |
| |||
Lines changed: 2 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
| 54 | + | |
| 55 | + | |
54 | 56 | | |
55 | 57 | | |
56 | 58 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
9 | | - | |
| 9 | + | |
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| |||
0 commit comments