Feature curated projects on homepage
Deploy production / deploy (push) Successful in 4s

This commit is contained in:
2026-08-12 06:40:57 -07:00
parent bd49e93abd
commit 4223691e43
7 changed files with 109 additions and 48 deletions
+11
View File
@@ -42,6 +42,8 @@ h1 { max-width: 650px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style:
.dek p { margin: 0; }
.activity-section { min-height: 370px; padding: 48px 0 72px; }
.homepage-discovery-grid { display: grid; grid-template-columns: minmax(0, 1.65fr) minmax(290px, 1fr); gap: 36px; align-items: start; }
.homepage-panel { min-width: 0; }
.section-heading { display: flex; justify-content: space-between; padding-bottom: 22px; border-bottom: 1px solid var(--rule); color: var(--muted); font-size: 10px; }
.timeline { position: relative; margin: 16px 0 0 7px; padding-left: 30px; border-left: 1px solid var(--rule); }
.timeline-entry { position: relative; padding: 0 0 30px; }
@@ -75,6 +77,14 @@ h1 { max-width: 650px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style:
.activity-more-button:focus-visible { border-color: var(--teal); color: var(--teal); }
.activity-more-button:disabled { cursor: default; opacity: .55; }
.featured-project-list { border-bottom: 1px solid var(--rule); }
.featured-project { padding: 22px 0 24px; }
.featured-project + .featured-project { border-top: 1px solid rgba(39,48,57,.65); }
.featured-project h2 { margin: 10px 0 5px; font-size: 21px; line-height: 1.3; }
.featured-project p { margin: 0 0 10px; color: var(--muted); font-size: 14px; line-height: 1.55; }
.featured-projects-empty { margin: 0; padding: 24px 0; color: var(--muted); }
.featured-projects-link { display: inline-block; margin-top: 22px; color: var(--teal); font: 500 10px var(--mono); letter-spacing: .12em; text-transform: uppercase; }
.card-grid { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 1px; background: var(--rule); border: 1px solid var(--rule); }
.entry-card { min-height: 245px; padding: 28px; background: var(--bg); }
.entry-tags { display: flex; flex-wrap: wrap; gap: 7px; margin: 16px 0; }
@@ -301,6 +311,7 @@ h1 { max-width: 650px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style:
.header-meta { display: none; }
.hero, .hero.compact { padding-block: 44px; }
h1 { font-size: 30px; }
.homepage-discovery-grid { grid-template-columns: 1fr; gap: 48px; }
.card-grid { grid-template-columns: 1fr; }
.tag-directory { grid-template-columns: 1fr; }
.tag-filter { padding-block: 18px; }
+1
View File
@@ -3,3 +3,4 @@ repository = https://git.labyricorn.com/Labyricorn/thinkloom-openai-hackathon.gi
web_url = https://git.labyricorn.com/Labyricorn/thinkloom-openai-hackathon
api_url = https://git.labyricorn.com/api/v1/repos/Labyricorn/thinkloom-openai-hackathon
branch = main
featured_order = 10
+8
View File
@@ -30,6 +30,14 @@ type = select
choices = active, released, maintained, archived
choice_labels = Active, Released, Maintained, Archived
[fields.featured]
label = Feature on homepage
type = boolean
[fields.featured_order]
label = Homepage feature order
type = integer
[fields.started]
label = Started
type = date
+21 -12
View File
@@ -123,13 +123,13 @@ def validate_url(value: str, field: str) -> str:
return value.rstrip("/")
def load_registry(site_root: Path) -> list[dict[str, str]]:
def load_registry(site_root: Path) -> list[dict[str, str | int]]:
registry_path = site_root / "configs" / "project-sources.ini"
parser = configparser.ConfigParser(interpolation=None)
if not parser.read(registry_path, encoding="utf-8"):
raise ProjectSourceError(f"project source registry is missing: {registry_path}")
sources: list[dict[str, str]] = []
sources: list[dict[str, str | int]] = []
for project_id in parser.sections():
if not SLUG_RE.fullmatch(project_id):
raise ProjectSourceError(f"invalid project id in registry: {project_id}")
@@ -141,15 +141,21 @@ def load_registry(site_root: Path) -> list[dict[str, str]]:
branch = section["branch"].strip()
if not re.fullmatch(r"[A-Za-z0-9._/-]+", branch) or ".." in branch:
raise ProjectSourceError(f"{project_id}: invalid branch")
sources.append(
{
"project_id": project_id,
"repository": validate_url(section["repository"].strip(), "repository"),
"web_url": validate_url(section["web_url"].strip(), "web_url"),
"api_url": validate_url(section["api_url"].strip(), "api_url"),
"branch": branch,
}
)
source: dict[str, str | int] = {
"project_id": project_id,
"repository": validate_url(section["repository"].strip(), "repository"),
"web_url": validate_url(section["web_url"].strip(), "web_url"),
"api_url": validate_url(section["api_url"].strip(), "api_url"),
"branch": branch,
}
if "featured_order" in section:
try:
source["featured_order"] = int(section["featured_order"])
except ValueError as exc:
raise ProjectSourceError(
f"{project_id}: featured_order must be an integer"
) from exc
sources.append(source)
if not sources:
raise ProjectSourceError("project source registry is empty")
return sources
@@ -566,7 +572,7 @@ def append_fields(original: bytes, fields: dict[str, Any]) -> bytes:
def materialize(
source: dict[str, str],
source: dict[str, str | int],
destination_root: Path,
files: dict[str, bytes],
records: dict[str, dict[str, str]],
@@ -630,6 +636,9 @@ def materialize(
"repository_language_tags": repository_language_tags,
"taxonomy_tags": taxonomy_tags,
}
if "featured_order" in source:
project_fields["featured"] = "yes"
project_fields["featured_order"] = source["featured_order"]
for source_path, data in files.items():
relative = PurePosixPath(source_path).relative_to(".labyricorn")
+1 -1
View File
@@ -9,7 +9,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=Inter:wght@400;500&family=Playfair+Display:ital,wght@0,600;0,700;1,500;1,600&display=swap" rel="stylesheet">
<link rel="icon" href="{{ '/static/favicon.svg'|url }}" type="image/svg+xml">
<link rel="stylesheet" href="{{ '/static/style.css'|url }}?v=10">
<link rel="stylesheet" href="{{ '/static/style.css'|url }}?v=11">
<script src="{{ '/static/tags.js'|url }}?v=1" defer></script>
<script src="{{ '/static/activity.js'|url }}?v=1" defer></script>
</head>
+62 -35
View File
@@ -8,42 +8,69 @@
<section class="activity-section">
<div class="shell">
<div class="section-heading">
<span>Recent Activity</span>
<span>Sorted by date</span>
</div>
<div class="timeline" id="recent-activity-list" data-activity-feed data-batch-size="6">
{% set entries = [] %}
{% for section_id in ['projects', 'articles', 'blog'] %}
{% for item in site.get('/' ~ section_id).children %}
{% if entries.append(item) %}{% endif %}
{% endfor %}
{% endfor %}
{% for project in site.query('/projects') %}
{% if project._model == 'project' %}
{% set devlog = site.get(project.path ~ '/devlog') %}
{% if devlog %}
{% for entry in devlog.children %}
{% if entries.append(entry) %}{% endif %}
<div class="homepage-discovery-grid">
<section class="homepage-panel" aria-labelledby="recent-activity-heading">
<div class="section-heading">
<span id="recent-activity-heading">Recent Activity</span>
<span>Sorted by date</span>
</div>
<div class="timeline" id="recent-activity-list" data-activity-feed data-batch-size="6">
{% set entries = [] %}
{% for section_id in ['articles', 'blog'] %}
{% for item in site.get('/' ~ section_id).children %}
{% if entries.append(item) %}{% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% endfor %}
{% for item in entries|sort(attribute='date', reverse=true) %}
<article class="timeline-entry accent-{{ item.kicker|lower }}" data-activity-item>
<div class="entry-meta">
<span class="label">{{ item.kicker }}</span>
<time datetime="{{ item.date }}">{{ item.date|dateformat('YYYY.MM.dd') }}</time>
</div>
<h2><a href="{{ item|url }}">{{ item.title }}</a></h2>
<p>{{ item.summary }}</p>
<a class="read-more" href="{{ item|url }}">Read entry →</a>
</article>
{% endfor %}
</div>
<div class="activity-controls" data-activity-controls hidden>
<p class="activity-status" data-activity-status aria-live="polite"></p>
<button class="activity-more-button" type="button" data-activity-more aria-controls="recent-activity-list">Show 6 more</button>
{% endfor %}
{% for project in site.query('/projects') %}
{% if project._model == 'project' %}
{% set devlog = site.get(project.path ~ '/devlog') %}
{% if devlog %}
{% for entry in devlog.children %}
{% if entries.append(entry) %}{% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% endfor %}
{% for item in entries|sort(attribute='date', reverse=true) %}
<article class="timeline-entry accent-{{ item.kicker|lower }}" data-activity-item>
<div class="entry-meta">
<span class="label">{{ item.kicker }}</span>
<time datetime="{{ item.date }}">{{ item.date|dateformat('YYYY.MM.dd') }}</time>
</div>
<h2><a href="{{ item|url }}">{{ item.title }}</a></h2>
<p>{{ item.summary }}</p>
<a class="read-more" href="{{ item|url }}">Read entry →</a>
</article>
{% endfor %}
</div>
<div class="activity-controls" data-activity-controls hidden>
<p class="activity-status" data-activity-status aria-live="polite"></p>
<button class="activity-more-button" type="button" data-activity-more aria-controls="recent-activity-list">Show 6 more</button>
</div>
</section>
<section class="homepage-panel featured-projects-panel" aria-labelledby="featured-projects-heading">
<div class="section-heading">
<span id="featured-projects-heading">Featured Projects</span>
<span>Curated work</span>
</div>
<div class="featured-project-list">
{% for project in site.query('/projects').filter(F._model == 'project').filter(F.featured == true).order_by('featured_order', '-date').limit(3) %}
<article class="featured-project">
<div class="entry-meta">
<span class="label">{{ project.status }}</span>
<time datetime="{{ project.date }}">{{ project.date|dateformat('YYYY.MM.dd') }}</time>
</div>
<h2><a href="{{ project|url }}">{{ project.title }}</a></h2>
<p>{{ project.summary }}</p>
<a class="read-more" href="{{ project|url }}">View project →</a>
</article>
{% else %}
<p class="featured-projects-empty">Selected projects will appear here.</p>
{% endfor %}
</div>
<a class="featured-projects-link" href="{{ '/projects'|url }}">View all projects →</a>
</section>
</div>
</div>
</section>
+5
View File
@@ -14,6 +14,7 @@ from project_sources import ( # noqa: E402
RepositoryNotPublicError,
append_fields,
fetch_json,
load_registry,
metadata_digest,
normalize_tag_value,
parse_record,
@@ -25,6 +26,10 @@ from project_sources import ( # noqa: E402
class ProjectSourceTests(unittest.TestCase):
def test_registry_feature_order_is_optional_and_numeric(self) -> None:
site_root = Path(__file__).resolve().parents[1]
self.assertEqual(load_registry(site_root)[0]["featured_order"], 10)
def test_registry_urls_must_be_public_https_without_credentials(self) -> None:
self.assertEqual(
validate_url("https://git.example.test/owner/repo.git", "repository"),