)
diff --git a/src/components/charts/SubmissionsTrendChart.jsx b/src/components/charts/SubmissionsTrendChart.jsx
index 8fbb7b4..bbbcded 100644
--- a/src/components/charts/SubmissionsTrendChart.jsx
+++ b/src/components/charts/SubmissionsTrendChart.jsx
@@ -4,7 +4,7 @@ function SubmissionsTrendChart({ data }) {
return (
-
+ {/* Legend's default itemSorter is 'value', which alphabetizes entries by
+ name — overriding the order series/ below are declared in (e.g.
+ "Not vibe-coded" would sort before "Vibe-coded"). Disable it to keep
+ declaration order instead. */}
+
{series.map(({ key, name, color }) => (
Data unavailable.
) : (
-
+ project.is_published && project.consent_to_display)
.map((project) => ({
...project,
diff --git a/src/index.css b/src/index.css
index 191cbac..1089a22 100644
--- a/src/index.css
+++ b/src/index.css
@@ -88,6 +88,33 @@ p {
margin: 0;
}
+.app-header {
+ text-align: center;
+}
+
+.app-subheader-block {
+ margin-top: 40px;
+ padding: 24px;
+ background: var(--accent-bg);
+ border-radius: 8px;
+ display: inline-block;
+}
+
+.app-subheader {
+ font-family: 'Cardo', var(--heading);
+ font-size: 28px;
+ color: var(--text-h);
+ margin: 0 0 8px;
+}
+
+.app-subheader-desc {
+ margin: 0 auto;
+ color: var(--text);
+ font-size: 20px;
+ line-height: 145%;
+ white-space: nowrap;
+}
+
code,
.counter {
font-family: var(--mono);
@@ -104,23 +131,22 @@ code {
}
.dashboard-controls {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ margin: 24px 0;
+ padding: 0 24px;
+}
+
+.dashboard-controls-row {
display: flex;
gap: 12px;
align-items: center;
- justify-content: flex-start;
- flex-wrap: wrap;
- margin: 24px 0;
- /* Anchors the row's left edge under the Developers logo's left edge.
- The logo (646px, centered in the full-width header) and this row (inside
- the sidebar-offset tab-content) live in different coordinate spaces, so
- matching them means re-deriving the logo's position here: half the
- leftover viewport width outside the logo, minus the sidebar's full
- rendered width (220px content + 24px padding + 1px border-right). */
- margin-left: max(0px, calc((100vw - 646px) / 2 - 245px));
}
.dashboard-controls input,
.dashboard-controls select {
+ flex: 1;
font: inherit;
padding: 8px 12px;
border: 1px solid var(--border);
@@ -129,13 +155,18 @@ code {
color: var(--text-h);
}
-.search-input {
- width: 280px;
+.dashboard-controls select {
+ appearance: none;
+ padding-right: 32px;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6' fill='none'%3E%3Cpath d='M1 1L5 5L9 1' stroke='%23555' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 14px center;
}
.project-count {
color: var(--text);
font-size: 14px;
+ margin-left: 6px;
}
.project-grid {
@@ -242,7 +273,7 @@ code {
padding: 2px 8px;
border-radius: 4px;
background: color-mix(in srgb, var(--category-color, var(--accent)) 14%, var(--bg));
- color: color-mix(in srgb, var(--category-color, var(--accent)) 70%, var(--text-h));
+ color: #000;
border: 1px solid color-mix(in srgb, var(--category-color, var(--accent)) 40%, var(--bg));
}
@@ -255,7 +286,22 @@ code {
/* fills whatever vertical space the back face leaves after the category tags */
flex: 1;
min-height: 0;
- overflow-y: auto;
+ overflow-y: scroll;
+ scrollbar-color: color-mix(in srgb, var(--text) 55%, transparent) transparent;
+ scrollbar-width: thin;
+}
+
+.project-card-desc::-webkit-scrollbar {
+ width: 8px;
+}
+
+.project-card-desc::-webkit-scrollbar-thumb {
+ background-color: color-mix(in srgb, var(--text) 55%, transparent);
+ border-radius: 4px;
+}
+
+.project-card-desc::-webkit-scrollbar-track {
+ background: transparent;
}
.app-body {
@@ -310,3 +356,7 @@ code {
flex-direction: column;
gap: 48px;
}
+
+.pie-chart .recharts-legend-item {
+ margin-right: 20px !important;
+}
diff --git a/src/utils/__tests__/submissionsTrend.test.js b/src/utils/__tests__/submissionsTrend.test.js
index 9ee26db..4a2d7c2 100644
--- a/src/utils/__tests__/submissionsTrend.test.js
+++ b/src/utils/__tests__/submissionsTrend.test.js
@@ -3,23 +3,24 @@ import assert from 'node:assert/strict'
import { getSubmissionsTrendByExperience, getSubmissionsTrendByVibeCoded } from '../submissionsTrend.js'
// Fixed "today" for every test below, so results are predictable instead of
-// depending on when the test happens to run. July 15 2026 in local time —
-// JS Date months are 0-indexed, so 6 means July.
-const referenceDate = new Date(2026, 6, 15) // July 2026
+// depending on when the test happens to run. August 15 2026 in local time —
+// JS Date months are 0-indexed, so 7 means August. Charts only render data
+// through the most recently *completed* month (July here), since August
+// itself isn't over yet.
+const referenceDate = new Date(2026, 7, 15) // August 2026
// --- getSubmissionsTrendByExperience -------------------------------------
// This function builds the data for ExperienceTrendChart: one row per
// month, with a count for each experience level (No Experience, Beginner,
// Intermediate, Advanced) in that month.
-test('getSubmissionsTrendByExperience returns [] when no project has a parseable date', () => {
- // Neither project has a real "Month Year" tag ("Date unspecified" and an
- // empty tags array both fail to parse), so there's nothing to chart —
- // the function should bail out to an empty array rather than a chart
- // full of zeros.
+test('getSubmissionsTrendByExperience returns [] when no project has a usable submission_date', () => {
+ // Neither project has a submission_date (null and missing both fail to
+ // parse), so there's nothing to chart — the function should bail out to
+ // an empty array rather than a chart full of zeros.
const projects = [
- { tags: ['Date unspecified'], technical_experience: 'None' },
- { tags: [], technical_experience: 'Beginner' },
+ { submission_date: null, technical_experience: 'None' },
+ { technical_experience: 'Beginner' },
]
assert.deepEqual(getSubmissionsTrendByExperience(projects, referenceDate), [])
})
@@ -30,7 +31,7 @@ test('getSubmissionsTrendByExperience spans from the earliest submission through
// one month that has data — June has no submissions, so it's zero-filled
// rather than skipped.
const projects = [
- { tags: ['May 2026'], technical_experience: 'None' },
+ { submission_date: '2026-05-10T10:00:00+00:00', technical_experience: 'None' },
]
const result = getSubmissionsTrendByExperience(projects, referenceDate)
@@ -47,11 +48,11 @@ test('getSubmissionsTrendByExperience buckets each level independently per month
// the others — e.g. May should show 1 "No Experience" AND 2 "Beginner"
// in the same row, and July's counts shouldn't leak into May's.
const projects = [
- { tags: ['May 2026'], technical_experience: 'None' },
- { tags: ['May 2026'], technical_experience: '<5 years' },
- { tags: ['May 2026'], technical_experience: '<5 years' },
- { tags: ['July 2026'], technical_experience: '5-10 years' },
- { tags: ['July 2026'], technical_experience: '10+ years' },
+ { submission_date: '2026-05-01T10:00:00+00:00', technical_experience: 'None' },
+ { submission_date: '2026-05-15T10:00:00+00:00', technical_experience: '<5 years' },
+ { submission_date: '2026-05-20T10:00:00+00:00', technical_experience: '<5 years' },
+ { submission_date: '2026-07-02T10:00:00+00:00', technical_experience: '5-10 years' },
+ { submission_date: '2026-07-25T10:00:00+00:00', technical_experience: '10+ years' },
]
const result = getSubmissionsTrendByExperience(projects, referenceDate)
@@ -62,13 +63,13 @@ test('getSubmissionsTrendByExperience buckets each level independently per month
])
})
-test('getSubmissionsTrendByExperience returns [] when every parseable project has unspecified experience', () => {
+test('getSubmissionsTrendByExperience returns [] when every dated project has unspecified experience', () => {
// The date parses fine, but technical_experience is blank, so
// getExperienceLevel can't map it to any of the 4 known levels. With no
// project contributing a real level, there's nothing meaningful to
// chart, so this should behave the same as "no data" (empty array).
const projects = [
- { tags: ['July 2026'], technical_experience: '' },
+ { submission_date: '2026-07-10T10:00:00+00:00', technical_experience: '' },
]
const result = getSubmissionsTrendByExperience(projects, referenceDate)
@@ -85,8 +86,8 @@ test('getSubmissionsTrendByExperience ignores unspecified-experience months when
// on this exact behavior.
const projects = [
// Unspecified experience, earlier date — should NOT push the chart's start back.
- { tags: ['January 2026'], technical_experience: '' },
- { tags: ['July 2026'], technical_experience: 'None' },
+ { submission_date: '2026-01-10T10:00:00+00:00', technical_experience: '' },
+ { submission_date: '2026-07-10T10:00:00+00:00', technical_experience: 'None' },
]
const result = getSubmissionsTrendByExperience(projects, referenceDate)
@@ -101,18 +102,18 @@ test('getSubmissionsTrendByExperience ignores unspecified-experience months when
// at the earliest real data), with a count of "Vibe-coded" vs.
// "Not vibe-coded" submissions per month.
-test('getSubmissionsTrendByVibeCoded returns 12 zero-filled months when no project has a parseable date', () => {
- const referenceDate = new Date(2026, 6, 15) // July 2026
+test('getSubmissionsTrendByVibeCoded returns 12 zero-filled months when no project has a usable submission_date', () => {
+ const referenceDate = new Date(2026, 7, 15) // August 2026 — most recently completed month is July
// Neither project has a usable date, so every month should come back
// zero-filled — but note this function always returns exactly 12 months
// (the trailing year up to referenceDate) regardless of whether there's
// any data, unlike getSubmissionsTrendByExperience above which returns []
// when there's nothing to show.
- const projects = [{ tags: ['Date unspecified'] }, {}]
+ const projects = [{ submission_date: null }, {}]
const result = getSubmissionsTrendByVibeCoded(projects, referenceDate)
assert.equal(result.length, 12)
- assert.equal(result[11].month, 'Jul 2026') // last of the 12 months is always referenceDate's month
+ assert.equal(result[11].month, 'Jul 2026') // last of the 12 months is always the most recently completed month
for (const entry of result) {
assert.equal(entry['Vibe-coded'], 0)
assert.equal(entry['Not vibe-coded'], 0)
@@ -120,15 +121,15 @@ test('getSubmissionsTrendByVibeCoded returns 12 zero-filled months when no proje
})
test('getSubmissionsTrendByVibeCoded buckets each project into the correct series for its month', () => {
- const referenceDate = new Date(2026, 6, 15) // July 2026
+ const referenceDate = new Date(2026, 7, 15) // August 2026 — most recently completed month is July
// 4 projects: 3 in July (2 vibe-coded, 1 not) and 1 in June (not
// vibe-coded). Checks that each project's vibe_coded boolean routes it
// into the right one of the two series, per month.
const projects = [
- { tags: ['July 2026'], vibe_coded: true },
- { tags: ['July 2026'], vibe_coded: true },
- { tags: ['July 2026'], vibe_coded: false },
- { tags: ['June 2026'], vibe_coded: false },
+ { submission_date: '2026-07-05T10:00:00+00:00', vibe_coded: true },
+ { submission_date: '2026-07-12T10:00:00+00:00', vibe_coded: true },
+ { submission_date: '2026-07-20T10:00:00+00:00', vibe_coded: false },
+ { submission_date: '2026-06-15T10:00:00+00:00', vibe_coded: false },
]
const result = getSubmissionsTrendByVibeCoded(projects, referenceDate)
@@ -139,15 +140,15 @@ test('getSubmissionsTrendByVibeCoded buckets each project into the correct serie
assert.deepEqual(june, { month: 'Jun 2026', 'Vibe-coded': 0, 'Not vibe-coded': 1 })
})
-test('getSubmissionsTrendByVibeCoded ignores projects with unparseable or missing dates', () => {
- const referenceDate = new Date(2026, 6, 15) // July 2026
- // 3 projects, only 1 with a real date ("July 2026"); the other 2 have an
- // unparseable date string and no tags at all, respectively. Both bad
- // ones should be silently dropped rather than counted or causing an
- // error — so the total across all months should be exactly 1.
+test('getSubmissionsTrendByVibeCoded ignores projects with a missing submission_date', () => {
+ const referenceDate = new Date(2026, 7, 15) // August 2026 — most recently completed month is July
+ // 3 projects, only 1 with a real submission_date; the other 2 have null
+ // and no submission_date field at all, respectively. Both bad ones should
+ // be silently dropped rather than counted or causing an error — so the
+ // total across all months should be exactly 1.
const projects = [
- { tags: ['July 2026'], vibe_coded: true },
- { tags: ['not a date'], vibe_coded: true },
+ { submission_date: '2026-07-10T10:00:00+00:00', vibe_coded: true },
+ { submission_date: null, vibe_coded: true },
{ vibe_coded: true },
]
const result = getSubmissionsTrendByVibeCoded(projects, referenceDate)
diff --git a/src/utils/submissionsTrend.js b/src/utils/submissionsTrend.js
index b3476ff..07b33fa 100644
--- a/src/utils/submissionsTrend.js
+++ b/src/utils/submissionsTrend.js
@@ -3,25 +3,23 @@ import { EXPERIENCE_LEVELS, getExperienceLevel } from './experience.js'
// The two series names used by the vibe-coded trend chart. Exported so the
// chart component can build its for each series without hardcoding
// the strings itself (keeps the "source of truth" for series names here).
-export const VIBE_CODED_SERIES = ['Not vibe-coded', 'Vibe-coded']
+export const VIBE_CODED_SERIES = ['Vibe-coded', 'Not vibe-coded']
const MONTH_NAMES = [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December',
]
-// Tag dates look like "July 2026" or "Date unspecified".
-// Turns that raw string into a { year, monthIndex } object, or null if it
-// doesn't match the expected "Month Year" shape (covers "Date unspecified",
-// missing tags, typos, etc. — anything we can't confidently parse).
-function parseTagMonth(rawDate) {
- const match = /^([A-Za-z]+) (\d{4})$/.exec(rawDate ?? '')
- if (!match) return null
+// submission_date is an ISO timestamp like "2026-07-19T14:54:00+00:00", or
+// null for projects that don't have one. Turns it into a { year, monthIndex }
+// object in local time, or null when there's nothing to parse.
+function parseSubmissionMonth(submissionDate) {
+ if (!submissionDate) return null
- const monthIndex = MONTH_NAMES.indexOf(match[1])
- if (monthIndex === -1) return null
+ const date = new Date(submissionDate)
+ if (Number.isNaN(date.getTime())) return null
- return { year: Number(match[2]), monthIndex }
+ return { year: date.getFullYear(), monthIndex: date.getMonth() }
}
// Turns { year, monthIndex } into a string like "2026-07" — used as a Map
@@ -31,30 +29,38 @@ function monthKey(year, monthIndex) {
return `${year}-${String(monthIndex + 1).padStart(2, '0')}`
}
-// Builds the 12 calendar months ending with referenceDate's month, oldest first.
-function last12Months(referenceDate) {
+// Builds the 12 calendar months ending with `end`, oldest first.
+function last12Months(end) {
const months = []
for (let i = 11; i >= 0; i--) {
- const d = new Date(referenceDate.getFullYear(), referenceDate.getMonth() - i, 1)
+ const d = new Date(end.year, end.monthIndex - i, 1)
months.push({ year: d.getFullYear(), monthIndex: d.getMonth() })
}
return months
}
+// Returns the most recently *completed* calendar month before referenceDate,
+// as a { year, monthIndex } pair — e.g. if referenceDate is any day in
+// August, this returns July, since August itself isn't over yet and its
+// submission count is still incomplete.
+function mostRecentCompletedMonth(referenceDate) {
+ const d = new Date(referenceDate.getFullYear(), referenceDate.getMonth() - 1, 1)
+ return { year: d.getFullYear(), monthIndex: d.getMonth() }
+}
+
// Builds the data for SubmissionsTrendChart: total submission count per
-// month, for the trailing 12 months ending at referenceDate.
+// month, for the trailing 12 months ending at the most recently completed
+// month before referenceDate.
export function getSubmissionsMonthlyTrend(projects, referenceDate = new Date()) {
- const months = last12Months(referenceDate)
+ const months = last12Months(mostRecentCompletedMonth(referenceDate))
// Start every month at 0 so months with no submissions still show up in
// the chart instead of being skipped entirely.
const counts = new Map(months.map(({ year, monthIndex }) => [monthKey(year, monthIndex), 0]))
for (const project of projects) {
- const [rawDate] = project.tags ?? []
-
- const parsed = parseTagMonth(rawDate)
- if (!parsed) continue // unparseable/missing date — skip this project
+ const parsed = parseSubmissionMonth(project.submission_date)
+ if (!parsed) continue // no submission_date — skip this project
const key = monthKey(parsed.year, parsed.monthIndex)
if (counts.has(key)) {
@@ -95,7 +101,7 @@ function monthsBetween(start, end) {
export function getSubmissionsTrendByExperience(projects, referenceDate = new Date()) {
const parsed = projects
.map((project) => ({
- month: parseTagMonth(project.tags?.[0]),
+ month: parseSubmissionMonth(project.submission_date),
level: getExperienceLevel(project.technical_experience),
}))
.filter((entry) => entry.month !== null) // drop projects with no usable date
@@ -116,7 +122,7 @@ export function getSubmissionsTrendByExperience(projects, referenceDate = new Da
return key < monthKey(earliestSoFar.year, earliestSoFar.monthIndex) ? entry.month : earliestSoFar
}, withLevel[0].month)
- const end = { year: referenceDate.getFullYear(), monthIndex: referenceDate.getMonth() }
+ const end = mostRecentCompletedMonth(referenceDate)
const months = monthsBetween(earliest, end)
// Each month starts with every experience level at 0, e.g.
@@ -146,11 +152,12 @@ export function getSubmissionsTrendByExperience(projects, referenceDate = new Da
// Builds the data for VibeCodedTrendChart: one row per month, with a
// submission count for each of the 2 series ("Vibe-coded" / "Not
-// vibe-coded") in that month. Always covers the trailing 12 months —
-// unlike getSubmissionsTrendByExperience above, it doesn't hunt for an
-// earliest-data month, so it can't return [] the way that one can.
+// vibe-coded") in that month. Always covers the trailing 12 months ending
+// at the most recently completed month — unlike getSubmissionsTrendByExperience
+// above, it doesn't hunt for an earliest-data month, so it can't return []
+// the way that one can.
export function getSubmissionsTrendByVibeCoded(projects, referenceDate = new Date()) {
- const months = last12Months(referenceDate)
+ const months = last12Months(mostRecentCompletedMonth(referenceDate))
// Each month starts with both series at 0.
const counts = new Map(
@@ -161,9 +168,8 @@ export function getSubmissionsTrendByVibeCoded(projects, referenceDate = new Dat
)
for (const project of projects) {
- const [rawDate] = project.tags ?? []
- const parsed = parseTagMonth(rawDate)
- if (!parsed) continue // unparseable/missing date — skip this project
+ const parsed = parseSubmissionMonth(project.submission_date)
+ if (!parsed) continue // no submission_date — skip this project
const key = monthKey(parsed.year, parsed.monthIndex)
if (!counts.has(key)) continue // outside the trailing-12-months window
diff --git a/src/utils/techUsed.js b/src/utils/techUsed.js
index 4b4333e..4906c1c 100644
--- a/src/utils/techUsed.js
+++ b/src/utils/techUsed.js
@@ -10,7 +10,7 @@ export const KNOWN_TECHNOLOGIES = [
{ label: 'Next.js', patterns: ['next.js', 'nextjs'] },
{ label: 'Vercel', patterns: ['vercel'] },
{ label: 'Python', patterns: ['python'] },
- { label: 'MCP', patterns: ['mcp'] },
+ { label: 'Sefaria MCP', patterns: ['mcp'] },
{ label: 'Supabase', patterns: ['supabase'] },
{ label: 'Deepgram', patterns: ['deepgram'] },
{ label: '.NET', patterns: ['.net'] },
From 050c581acb30199ba780da490e45a58cd0b10d20 Mon Sep 17 00:00:00 2001
From: Sefaria Intern
Date: Mon, 10 Aug 2026 13:06:17 +0300
Subject: [PATCH 2/3] Fetch projects only from the production Sefaria API
The local dev API merge is no longer needed now that the production
endpoint is up to date. Also fixes .gitignore: the unanchored data/
rule was accidentally shadowing src/data/ too; the anchored /data/
rule already covers the intended raw-dump exclusion.
Co-Authored-By: Claude Sonnet 5
---
.gitignore | 3 ---
src/data/fetchProjects.js | 14 ++------------
2 files changed, 2 insertions(+), 15 deletions(-)
diff --git a/.gitignore b/.gitignore
index 9228279..4072425 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,9 +15,6 @@ dist-ssr
docs/superpowers/
.worktrees/
-# Raw local data dumps (may contain sensitive info, never commit)
-data/
-
# Editor directories and files
.vscode/*
!.vscode/extensions.json
diff --git a/src/data/fetchProjects.js b/src/data/fetchProjects.js
index c13bbfa..dce3b82 100644
--- a/src/data/fetchProjects.js
+++ b/src/data/fetchProjects.js
@@ -1,6 +1,5 @@
import { getCategories } from '../utils/categories.js'
-const LOCAL_API_URL = 'http://localhost:8000/api/powered-by'
const PROD_API_URL = 'https://www.sefaria.org/api/powered-by'
async function fetchProjectList(url) {
@@ -15,18 +14,9 @@ async function fetchProjectList(url) {
}
export async function fetchProjects() {
- const [localProjects, prodProjects] = await Promise.all([
- fetchProjectList(LOCAL_API_URL),
- fetchProjectList(PROD_API_URL),
- ])
+ const projects = await fetchProjectList(PROD_API_URL)
- // later entries win ties, so prod overwrites local on matching project_name
- const byName = new Map()
- for (const project of [...localProjects, ...prodProjects]) {
- byName.set(project.project_name, project)
- }
-
- return [...byName.values()]
+ return projects
.filter((project) => project.is_published && project.consent_to_display)
.map((project) => ({
...project,
From 17c4d6173cd0a1307b4d1df9180aeff1e54d206a Mon Sep 17 00:00:00 2001
From: Sefaria Intern
Date: Tue, 11 Aug 2026 10:19:01 +0300
Subject: [PATCH 3/3] Fix design/code-review feedback: UTC month bucketing,
wrapping subheader, safe legend sorter
- submissionsTrend.js: bucket submission months by UTC instead of local
time so results don't shift depending on the viewer's timezone
- index.css: allow the subheader description to wrap instead of forcing
nowrap, which caused horizontal overflow on narrow viewports
- LineChart.jsx: replace itemSorter={null} with a function that sorts
legend items by their declared series order, since Recharts expects
itemSorter to be callable
Co-Authored-By: Claude Sonnet 5
---
src/components/charts/types/LineChart.jsx | 14 +++++++++++---
src/index.css | 1 -
src/utils/submissionsTrend.js | 10 ++++++----
3 files changed, 17 insertions(+), 8 deletions(-)
diff --git a/src/components/charts/types/LineChart.jsx b/src/components/charts/types/LineChart.jsx
index 986f2fc..02c9939 100644
--- a/src/components/charts/types/LineChart.jsx
+++ b/src/components/charts/types/LineChart.jsx
@@ -16,9 +16,17 @@ function LineChart({ data, series, title, description, height = 360 }) {
{/* Legend's default itemSorter is 'value', which alphabetizes entries by
name — overriding the order series/ below are declared in (e.g.
- "Not vibe-coded" would sort before "Vibe-coded"). Disable it to keep
- declaration order instead. */}
-
+ "Not vibe-coded" would sort before "Vibe-coded"). itemSorter must be a
+ function (or a string key) — Recharts calls it unconditionally, so
+ passing null risks breaking rather than disabling the sort. Sort by
+ each item's index in `series` instead, to preserve declaration order;
+ unmatched items (shouldn't normally happen) go last. */}
+