Skip to content
Merged
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
56 changes: 54 additions & 2 deletions website/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,15 @@ A static documentation site published to GitHub Pages.
## Where things live

- `src/content/docs/` — all user-facing pages (`.md` and `.mdx`)
- `introduction/`, `getting-started/`, `guides/`, `reference/`, `resources/`
- `introduction/`, `getting-started/`, `guides/`, `reference/`,
`resources/`, `community/`
- `src/content/articles/`, `src/content/talks/` — one YAML file per
published article / talk, schema-validated by the `articles` and
`talks` collections in `src/content.config.ts` (see
"Articles & talks" below)
- `src/components/community/` — `ArticleList`, `TalkList`,
`TalkCard`, and `CommunityTeaser` (the landing-page section), all
reading those collections
- Sidebar order is defined in `astro.config.mjs`, not by directory order
- `src/openapi/scope-openapi.json` — committed artifact generated from
the Scope API's OpenAPI registry; drives the auto-generated REST
Expand Down Expand Up @@ -152,6 +160,50 @@ Sidebar order is set in `astro.config.mjs`. Adding a new page
requires updating the sidebar array. The auto-generated REST API
groups are spread via `...openAPISidebarGroups`.

### Articles & talks

The `community/articles-and-talks` page and the "From the community"
section on the landing page are generated from two content
collections. To add an entry, add one YAML file. No code changes are
needed.

- **Article**: `src/content/articles/<title-slug>.yaml`
```yaml
title: Building AX evals that actually work
url: https://developer.microsoft.com/blog/building-ax-evals-that-actually-work/
publication: Microsoft for Developers # blog name
authors: # as credited, byline order
- firstName: Waldek
lastName: Mastykarz
position: Principal Developer Advocate
date: 2026-07-15 # publish date (optional)
```
- **Talk**: `src/content/talks/<yyyy-mm-dd>-<event-slug>.yaml`
```yaml
title: "From Findings to Fixes: ..."
speakers: # same shape as authors
- firstName: Jay
lastName: Gordon
position: Senior Program Manager, Azure Cosmos DB
event: Global AI New York
venue: Microsoft Lafayette, New York City
date: 2026-09-21
eventUrl: https://globalai.community/e/783bfa20 # GAIC event page
youtubeId: SxaKOmqX-rk # omit while pending
```

Take title, blog name, authors (name and position from the article's
author section), and publish date from the article page itself.
Take speaker positions from the event page or the speaker's event
profile (e.g. their Luma bio).
If a date can't be confirmed, leave `date` out; undated articles
sort last. A talk without `youtubeId` shows "Video coming soon".
Both lists sort newest first. A missing field, bad URL, or bad date
fails `pnpm run build`. Article and event links are external, so the
components open them in a new tab (`target="_blank"
rel="noopener noreferrer"`) with a screen-reader "(opens in a new
tab)" hint; keep that pattern for any new external link.

### Style

- Hard-wrap prose at ~70–80 columns for readable diffs.
Expand All @@ -177,7 +229,7 @@ pnpm run refresh:openapi # generate the OpenAPI snapshot from scope-core
Both `pnpm test` and `pnpm run build` must pass. The public build uses
`SITE=https://microsoft.github.io BASE_PATH=/scope pnpm run build`;
exercise that configuration when changing links or deployment settings,
not just the local `/` default. The current snapshot produces **200
not just the local `/` default. The current snapshot produces **203
pages**, including the generated API reference. An unexpected drop in
page count can indicate a content collection file failed to parse.

Expand Down
21 changes: 15 additions & 6 deletions website/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@ The product and this documentation site live in
├── public/ # static assets
├── src/
│ ├── assets/
│ ├── content/docs/ # all user-facing pages (.md / .mdx)
│ │ ├── introduction/
│ │ ├── getting-started/
│ │ ├── guides/
│ │ ├── reference/
│ │ └── resources/
│ ├── components/community/ # article/talk lists + landing teaser
│ ├── content/
│ │ ├── docs/ # all user-facing pages (.md / .mdx)
│ │ │ ├── introduction/
│ │ │ ├── getting-started/
│ │ │ ├── guides/
│ │ │ ├── reference/
│ │ │ ├── resources/
│ │ │ └── community/
│ │ ├── articles/ # one YAML per published article
│ │ └── talks/ # one YAML per talk
│ ├── openapi/scope-openapi.json # artifact generated from the API registry
│ ├── plugins/
│ │ ├── remark-base-path.mjs # applies the deployment base to internal links
Expand Down Expand Up @@ -72,6 +77,10 @@ Sidebar order is defined in `astro.config.mjs`, not by directory order.
`scope-core` checkout, so root workspace dependencies must be
installed first.

- To list a new article or talk on the Community page, add one YAML
file under `src/content/articles/` or `src/content/talks/`. See
"Articles & talks" in [AGENTS.md](AGENTS.md) for the fields.

See [AGENTS.md](AGENTS.md) for conventions, the source-of-truth
policy (everything factual must be grounded in scope-core), and
where to look in scope-core for any given topic.
Expand Down
4 changes: 4 additions & 0 deletions website/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ export default defineConfig({
{ label: 'Concepts', slug: 'introduction/concepts' },
],
},
{
label: 'Community',
items: [{ label: 'Articles & talks', slug: 'community/articles-and-talks' }],
},
{
label: 'Getting Started',
items: [
Expand Down
80 changes: 80 additions & 0 deletions website/src/components/community/ArticleList.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import Byline from './Byline.astro';
import { formatDate, getArticles, isoDate } from './community';

interface Props {
/** Show only the N most recent articles. */
limit?: number;
}

const { limit } = Astro.props;
const all = await getArticles();
const articles = limit === undefined ? all : all.slice(0, limit);
---

<ul class="community-articles not-content">
{
articles.map(({ data }) => (
<li class="community-article">
<a
class="community-article__title"
href={data.url}
target="_blank"
rel="noopener noreferrer"
>
{data.title}
<span class="sr-only"> (opens in a new tab)</span>
</a>
<span class="community-article__authors">
<Byline people={data.authors} />
</span>
<span class="community-article__meta">
{data.publication}
{data.date && (
<>
{' · '}
<time datetime={isoDate(data.date)}>{formatDate(data.date)}</time>
</>
)}
</span>
</li>
))
}
</ul>

<style>
.community-articles {
list-style: none;
margin: 1rem 0 0;
padding: 0;
border-top: 1px solid var(--sl-color-hairline);
}
.community-article {
display: flex;
flex-direction: column;
gap: 0.25rem;
margin: 0;
padding: 0.9rem 0;
border-bottom: 1px solid var(--sl-color-hairline);
}
.community-article__title {
font-weight: 600;
line-height: 1.4;
color: var(--sl-color-white);
text-decoration: none;
}
.community-article__title:hover {
color: var(--sl-color-text-accent);
text-decoration: underline;
}
.community-article__authors {
color: var(--sl-color-gray-2);
}
.community-article__meta {
font-size: var(--sl-text-sm);
color: var(--sl-color-gray-3);
}
</style>
36 changes: 36 additions & 0 deletions website/src/components/community/Byline.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import type { Person } from './community';

interface Props {
people: Person[];
}

const { people } = Astro.props;
---

<span class="community-byline">
{
people.map((p, i) => (
<span class="community-byline__person">
{i > 0 && <span aria-hidden="true">{' · '}</span>}
{/* Keep on one line: newlines between these tags render as a space before the comma. */}
<span class="community-byline__name">{`${p.firstName} ${p.lastName}`}</span>{', '}<span class="community-byline__position">{p.position}</span>
</span>
))
}
</span>

<style>
.community-byline {
display: block;
font-size: var(--sl-text-sm);
line-height: 1.5;
}
.community-byline__name {
font-weight: 500;
white-space: nowrap;
}
</style>
37 changes: 37 additions & 0 deletions website/src/components/community/CommunityTeaser.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import ArticleList from './ArticleList.astro';
import TalkCard from './TalkCard.astro';
import { getTalks } from './community';

const talks = await getTalks();
const featured = talks.find((t) => t.data.youtubeId) ?? talks[0];
const allHref = `${import.meta.env.BASE_URL.replace(/\/$/, '')}/community/articles-and-talks/`;
---

<section class="scope-section scope-community">
<div class="scope-section__head">
<span class="scope-section__eyebrow">From the community</span>
<h2>Articles &amp; talks</h2>
<p>What the community has learned evaluating agentic product experiences with Scope, from the blog and the stage.</p>
</div>

<div class="scope-community__grid">
<div class="scope-community__col">
<h3 class="scope-community__label">Latest articles</h3>
<ArticleList limit={3} />
</div>
{
featured && (
<div class="scope-community__col">
<h3 class="scope-community__label">Featured talk</h3>
<TalkCard talk={featured} />
</div>
)
}
</div>

<a class="scope-community__all" href={allHref}>See all articles &amp; talks →</a>
</section>
109 changes: 109 additions & 0 deletions website/src/components/community/TalkCard.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
---
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import Byline from './Byline.astro';
import { formatDate, isoDate, type Talk } from './community';

interface Props {
talk: Talk;
}

const { data } = Astro.props.talk;
---

<article class="community-talk not-content">
<div class="community-talk__media">
{
data.youtubeId ? (
<iframe
src={`https://www.youtube-nocookie.com/embed/${data.youtubeId}`}
title={`Video: ${data.title}`}
loading="lazy"
allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen
/>
) : (
<div class="community-talk__pending" role="img" aria-label="Video coming soon">
<svg viewBox="0 0 24 24" width="36" height="36" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<rect x="2" y="5" width="15" height="14" rx="2" />
<path d="m17 10 5-3v10l-5-3" />
</svg>
<span>Video coming soon</span>
</div>
)
}
</div>
<div class="community-talk__body">
<h3 class="community-talk__title">{data.title}</h3>
<p class="community-talk__speakers"><Byline people={data.speakers} /></p>
<p class="community-talk__meta">
<a href={data.eventUrl} target="_blank" rel="noopener noreferrer">
{data.event}
<span class="sr-only"> (opens in a new tab)</span>
</a>
{' · '}
{data.venue}
{' · '}
<time datetime={isoDate(data.date)}>{formatDate(data.date)}</time>
</p>
</div>
</article>

<style>
.community-talk {
display: flex;
flex-direction: column;
min-width: 0;
border: 1px solid var(--sl-color-hairline);
border-radius: 0.75rem;
overflow: hidden;
background: var(--sl-color-bg);
}
.community-talk__media {
width: 100%;
aspect-ratio: 16 / 9;
background: var(--sl-color-gray-6);
}
.community-talk__media iframe {
display: block;
width: 100%;
height: 100%;
border: 0;
}
.community-talk__pending {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.6rem;
height: 100%;
color: var(--sl-color-gray-3);
font-size: var(--sl-text-sm);
font-weight: 600;
letter-spacing: 0.02em;
}
.community-talk__body {
padding: 1rem 1.15rem 1.15rem;
}
.community-talk__title {
margin: 0;
font-size: var(--sl-text-lg);
font-weight: 600;
line-height: 1.35;
color: var(--sl-color-white);
}
.community-talk__speakers {
margin: 0.4rem 0 0;
color: var(--sl-color-gray-2);
}
.community-talk__meta {
margin: 0.35rem 0 0;
font-size: var(--sl-text-sm);
color: var(--sl-color-gray-3);
}
.community-talk__meta a {
color: var(--sl-color-text-accent);
}
</style>
Loading
Loading