Spaces:
Running
Running
Commit History
-
fix(ui): close the gap above the project name Running
Reported 2026-09-15 with a screenshot. The row that carries the expand button was rendered at all times so that toggling the sidebar would not shift the page. With the sidebar open it had nothing in it, which left an empty strip above the project name and set the heading a few pixels below the logo across the divider, where the eye reads them as a pair. It renders only while the sidebar is collapsed now, and the main pane's top padding is 7px rather than 12px, which puts the project name on exactly the same line as the logo (measured: 5px out before, 0 now). The cost is that toggling shifts the content by one row, which is the cheaper of the two.cdc4d17
-
feat(ui): account chip and menu at the foot of the sidebar
Asked for with ChatGPT's sidebar as the reference (2026-09-15). When the banner came off, its contents landed in the sidebar as a loose row of links with the account beside them. They now hang off one chip: avatar, name, and the kind of account, opening upward into About, Guide, the lab-only pages, the admin link, and sign in or out. The line under the name is ChatGPT's plan slot; ours says lab member, external user, maintainer or PI, which is what actually changes what you can do here. Signed out it reads "Not signed in / Sign in to keep your work", so the chip still makes the case the old Sign in button did. Escape closes the menu and stops there, deliberately: the drawer also closes on Escape, and one key dismissing both would leave a phone user staring at a workspace they did not ask for. Verified at 1280 and 390 px, signed in and out: the menu opens upward, stays inside the viewport, closes on Escape and on an outside click, and the drawer stays open underneath.ecf3b3a -
feat(ui): drop the dark banner; the sidebar is the chrome now
"Every modern website doesn't have an explicit header these days, ours looks old school" (2026-09-15). The banner sat on every screen, carried the product's full name, and cost vertical space on a phone to say something the landing page already says. The dashboard now has no top chrome at all, the way ChatGPT and Claude have none: the sidebar holds the logo, the guides, the admin link and the account, and the content area starts at the content. The landing page and /admin have no sidebar, so they keep a bar, but a light and quiet one with no tagline. The three static doc pages get the same treatment, and their inline logo strokes with currentColor, which is what lets one mark serve both their light and dark themes. Two things this forced, both right anyway. The dashboard became a real app shell (100vh, each pane scrolling inside it): stretched by a long page, the sidebar's account row and New project button had drifted to the bottom of the document instead of sitting at the bottom of…3967e32 -
feat(ui): the project sidebar closes, and is a drawer on phones
Asked for 2026-09-15: closable like ChatGPT or Claude. A panel button at the left of the header hides and shows it, aria-expanded and all, and the choice is remembered. It only renders where a sidebar exists, so not on the landing page and not on /admin. Wide screens: the sidebar slides out of the flex row on a negative margin rather than animating to width 0, so nothing inside it reflows on the way out and the workspace takes the space. Content sitting off-screen to the LEFT adds no page scroll, which a width animation on the inner content would have risked. Phones: it becomes a drawer over the workspace and starts closed regardless of the remembered choice. It used to be a full-width block stacked ABOVE the workspace, so every phone visit began by scrolling past the project list to reach the analysis. Three ways out, because the drawer covers the header's own toggle: tapping a project (you picked it to look at it), tapping the backdrop, or Escape. Crossing from wide to narrow close…e360d80
-
feat(ui): cite link under a run's reproducibility record
Ali Hajian, 2026-09-10, after trying the landing-page button: the results page is the other place it belongs. He is right - the reproducibility record is what someone is looking at while writing their methods section, so the citation should be within reach there rather than a trip back to the landing page. The dialog mounts inside ResultsView with its own state rather than being lifted into App and threaded through Workspace: nothing between them needs to know it exists. The landing-page footer keeps its own copy for the same reason. The site footer still does NOT follow people into the results view; that was a deliberate call about keeping the working area uncluttered, and this is one muted line, not a footer.9bde790
-
docs: how to cite the platform
README gains a "How to cite" section with the three references, the DECISIONS entry records why the strings live in one backend module (and that the author line is the PI's to confirm), and the changelog describes the button. Also corrects the README's stale test count: 137 to 174.e867cf8 -
feat(ui): "Cite this platform" in the landing-page footer
Ali Hajian, 2026-09-09: somewhere inconspicuous to get a citation, the way Google Scholar's Cite button works. The footer of the landing page is that place - there for anyone writing a paper, invisible to anyone who is not. The dialog mirrors Scholar's panel in this app's language: five styles, each row a copy button, then BibTeX and RIS for reference managers. Under the styles it names the two CCR papers, because results from here rest on them and a methods section needs all three. Every string comes from GET /api/citation, so the component formats nothing. Two things measured rather than assumed. The clipboard: it needs a secure context and a focused document, so a failure selects the reference instead and says which keys to press, rather than leaving the click doing nothing. And the phone rules had to move BELOW the base .cite-* rules instead of joining the 700px block above them: same specificity, last one in the file wins, and from up there they silently lost (the row kept a 74p…61bbb51 -
feat(cite): one canonical citation for the platform and the CCR papers
Ali Hajian asked for a Google Scholar style Cite button (2026-09-09). The strings behind it live in app/citation.py and nowhere else: the dialog, the BibTeX and RIS exports, and the repo's new CITATION.cff all read from there, and a test fails if the .cff drifts. A citation that says two different things in two places is worse than no button. GET /api/citation is public, since the dialog sits on the landing page that signed-out visitors see. It returns the platform reference in APA, MLA, Chicago, Harvard and Vancouver, the two CCR papers a methods section needs beside it, and BibTeX and RIS containing all three. PLATFORM_VERSION moves to app/version.py so citation.py and jobs.py read the same constant: the version a paper cites is then the version stamped into the run metadata and the reproduction script that produced its numbers. Both papers were taken from Crossref, not from memory: 10.31234/osf.io/m93pd (Atari, Omrani & Dehghani 2023) and 10.18653/v1/2024.emnlp-main.151 (Chen, Li…b64a521
-
docs: record the phone-layout pass
Changelog entry for the results-view and guide fixes, and the one place deliberately left alone: the /admin users table, which is a staff tool used on a laptop.1e38d5d -
fix(guide): wrap the reproduction commands on a phone
The page had no `pre` styling at all, so the two-line pip/python block set the layout width: 485px inside a 390px screen. Every paragraph on the guide then scrolled sideways, and the fixed "Top" link sat off the right edge. The block scrolls at its own edge now and the commands wrap rather than overflow.e2d93c2 -
fix(ui): fit the results views on a phone screen
Reported by Ali Hajian 2026-09-09, after yesterday's runs-table fix: the rest of the app should be usable on a phone too. Three problems at 360 and 390 px, all from rules written for a desktop table. The 820px block sets `table.docs { min-width: 520px }` so wide tables scroll instead of squashing. Right for the admin tables; wrong for the two results tables, which sit in a 328px card and so ALWAYS needed a sideways drag to read a text or a correlation. Score + Text now fits once the text wraps, and a two- or three-construct interrelations matrix fits outright; a wider matrix still scrolls inside .table-wrap, which is what a matrix has to do on any screen. Worse, a multi-construct results page scrolled sideways AS A PAGE, so every paragraph on it moved: each construct's summary line carries .picker-meta, which is nowrap, and "mean 0.133 · SD 0.082 · 6 items · CSV columns mfq_2_equality_*" is wider than a phone. It wraps under the construct name now. Same treatment for the selected-con…5b8f9e6
-
fix(ui): rework the sample-corpus section of Step 1
It looked bolted on, and it was: a grey slab inside the white card, the only element in Step 1 that was not a labelled field like the controls above it, reached through a collapsed link. Now it is the second branch of the step. An "or" rule separates it from the upload row, and it reuses the same .field label and control shape as Upload file / Corpus / Text column, so it belongs to the form instead of sitting in a box inside it. The select, the info button and Use share one height and one radius rather than being three different shapes. The collapse toggle is gone. It existed to keep the step quiet, but that is what hid the corpus name in the first place, and the picker is one row tall now, so there is nothing to hide. Scope is deliberately just this section; the rest of the workspace is untouched. Verified in a real browser at 1280 and 390 wide. That caught a mistake worth noting: an earlier edit had swallowed the closing brace of the 700px media query, so the new rules only appli…db5d011 -
fix(ui): show the sample corpus by default; stack the runs table on phones
Both reported 2026-09-08. The PI could not find CAMEL on the dev site, and a lab member hit the same thing plus a mobile layout problem. The sample-data panel started collapsed, so the only words on screen were "Use sample data" - the corpus is not named until you expand it, which is no help to someone looking for CAMEL. It now starts open. That is cheap because the panel is one row tall since the dropdown rework; the earlier objection was to two large cards, which no longer applies. On a phone the runs table pushed Status and View results past the right edge, so reaching the two things you open that table for meant scrolling sideways. Each run is now a stacked card with every field labelled from data-label. Nothing is dropped, and there is no horizontal scroll. Three things this walked into, all found by measuring in a real browser rather than reading the CSS: - the base rules are `table.docs td`, which outranks a bare `.docs td` and silently kept the table padding; - `@media (m…e639e13
-
docs(corpus): record the CAMEL licence; the dataset link is fine
Correcting an earlier claim of mine: the dataset link is NOT broken. The HF repo is public and GATED, not private - the page returns 200 for anyone, and only file downloads require signing in and accepting the terms, which is ordinary for a research dataset. I had tested the file URL and generalised it to the page. Checking that surfaced something that does matter. CAMEL is licensed CC BY-NC 4.0, and nothing in the repo said so, while README states the project is MIT and sample_data/camel_sample.csv - 999 rows of that corpus - sits in the tree. Nothing here relicenses it, but a reader had no way to know the sample carries different terms from the code around it. Examples now record their own licence. It shows in the info dialog and rides in run metadata next to the citation, so a researcher who exports results has the terms in the same file as the scores. README and sample_data/README name CC BY-NC 4.0 for the bundled sample explicitly and separate it from MIT, and the guide states i…5411f5f -
fix(storage): do not require server-side copy for example corpora
Loading the full CAMEL corpus 500'd on the lab's deployment. The cause is not ours: R2 CopyObject is unavailable on that bucket, answering NoSuchKey for an object head_object resolves in the same session and with both CopySource forms. The dev account copies the identical 38 MB file without complaint, so this varies by account and cannot be relied on. The server-side copy was only ever an optimisation - it avoids reading 38 MB back out through the app per visitor. The caller has already downloaded the file to parse it, so the fallback is to upload that local copy: one upload, no extra fetch, and it works on any S3-compatible backend. Verified against the lab's bucket, where it now succeeds and leaves the master object intact. Note for whoever adds the next deployment: dev and prod are separate Cloudflare accounts, so the master file needs uploading once per account with scripts/upload_example_corpus.py. The catalogue only stores the key. Co-Authored-By: Claude Opus 5 (1M context) <n…82c5ea2 -
fix(ui): sample data picker is a dropdown with an info button
Choosing which corpus to run is a choice between mutually exclusive options, so it is a dropdown rather than a stack of rows: adding a dataset later costs one more option instead of another block of Step 1, and the panel stays one line tall whatever the catalogue holds. The per-entry About link becomes a single circular info button sized to the other controls, so it reads as part of the row rather than a glyph dropped into it. The dialog it opens now carries the CAMEL mark beside the name; the logo belongs there, on the dataset being described, and not in the picker, which lists several corpora and would imply they share a source. The selected entry keeps its one-line summary under the row, and an entry this instance cannot run is disabled in the dropdown, labelled "unavailable here", with the reason underneath. Drops the now-unused .samples-list, .sample-row, .sample-text, .sample-name, .sample-meta and .sample-actions rules rather than leaving them behind. Co-Authored-By: Claude …69aff04 -
feat(library): settle the last two wording questions; CAMEL logo
Both decisions confirmed with the PI on 2026-09-07, so no construct in the library is left without a verification decision. IPIP keeps its leading "I". Worth recording why the question dissolved: the platform never added it. import_from_xlsx.py only strips whitespace, and the items arrived with the "I" already present in the commit that first created the library, from lib.xlsx. The IPIP source really does print bare stems ("Feel little concern for others") with the subject supplied by its instruction header, so the reviewer's observation was right about the state and wrong about the cause: this is a transcription convention in the lab's own spreadsheet. The five constructs are verified with that provenance recorded on each. K10 is retired rather than rewritten. Its items are fragments under a shared header, and CCR embeds item text directly, so "worthless?" is a poor stand-in for the construct. Archived, not deleted: constructs are append-only and any run that used it must still reso…726d852 -
fix(ui): sample data as a small entry point under the upload row
The PI's framing for CAMEL is a small "use sample data" call to action: from it a visitor sees the two available sizes, and can read about the data if they want to. The dropdown row did the opposite - it sat above the upload control and read as the first thing to do. Step 1 is now upload first. Under the row, one muted line ("No data of your own? Use sample data") expands in place into a two-row list: name, size, language, a one-line fit note, About, Use. About opens the provenance dialog (description, text column, preprocessing, citation, dataset link), which carries its own Use button so a reader can decide from there; Escape and the backdrop close it. The panel closes once a corpus is loaded, and the Corpus select echoes the sample's name rather than its filename. Sizes an instance cannot run stay listed with the reason and a disabled Use. Picking a size already in the project selects that copy instead of copying the corpus again, so a second click cannot duplicate 38 MB in the bu…0b5c981 -
fix(ui): one compact corpus row instead of two large cards
Three problems with the first cut, all fair. CAMEL appeared twice, as two "corpora". It is one dataset in two sizes, so the sizes are now options in a single dropdown and it reads as one dataset. It was badged DEFAULT, which overstates it. The PI asked for CAMEL to be "available and ready-to-use"; that is a starting point, not a claim that this is THE corpus. The field is renamed default -> preselected, it decides ordering and what loads first, and nothing is badged. The two expanded cards took most of Step 1 and pushed the upload control below the fold, which is the wrong hierarchy: most researchers bring their own data. Step 1 is now one labelled row - dropdown, info button, action - and the provenance (description, text column, preprocessing, citation, dataset link) moved into a modal behind the info button, where it is one click away without occupying the step. Also: the listing now knows this instance's row ceiling. The full corpus is 57,174 rows against a deployed CCR_MAX_ROW…bb4c28d -
fix(anon): owner key overflowed owner_user_id, 500ing anonymous writes
Live outage on both deployments since spec 0009 shipped on 2026-09-04: POST /api/projects and POST /api/constructs returned 500 for anyone not signed in, which is the entire anonymous try-it path. ANON_PREFIX + secrets.token_hex(16) is 37 characters and both owner_user_id columns are String(32). SQLite ignores VARCHAR limits so all 162 tests passed locally; PostgreSQL enforces them, so every anonymous insert was rejected. Signed-in users were unaffected because a user id is exactly 32 characters, which is why nobody reported it - lab members are signed in. Shortened the session id to 13 bytes (104 bits of entropy, key length 31) rather than widening the column. A type change is not additive, so auto_migrate_sqlite would not apply it and both deployed databases would have needed hand-run DDL; this deploys as-is. No data is lost because no anon: row was ever successfully written. The regression test reads the column width from the model rather than hardcoding it, and asserts against t…a7f2409 -
feat(corpus): offer the full CAMEL corpus from R2, raise the row ceiling
Follows spec 0010, which shipped only a 999-text sample because the full corpus could not be ingested. CCR_MAX_ROWS goes to 100,000, which was the code default all along; the three docs that variously claimed 20,000 and 50,000 are corrected to agree with it and with each other. That removes the ceiling that made the 57,174-row corpus un-ingestible. The full corpus is served from R2 under examples/, not the repo: 38 MB would ride in the Docker image and the Space repo on every deploy. It is deliberately NOT fetched from Hugging Face at runtime - that dataset returns 401, so fetching it would mean shipping lab credentials in a public app. scripts/upload_example_corpus.py puts it there and prints the row count the catalogue entry has to declare. Selection downloads the master once to parse, then makes the user's copy with a server-side copy_object, so 38 MB does not travel back out through the application for every visitor. Verified against the live bucket: 7.0s fetch, 0.7s parse of 57…88b1ea9 -
feat(corpus): CAMEL as a built-in example corpus [spec 0010]
PI request from 2026-08-19: make the lab's CAMEL corpus the example data on the site so visitors can try CCR without uploading anything. Corpus details from Aliah 2026-09-06. Step 1 now offers an example corpus above the upload control. One click loads it, selects the declared text column, and the run proceeds through the normal ingest path, so nothing downstream changes. The citation and a link to the full dataset sit on the card, and a run on an example writes a corpus_source block into its metadata carrying the citation, source URL and preprocessing note: results produced on someone else's corpus have to stay attributable in the same file as the scores. Ships a 999-text SAMPLE rather than the full corpus, because the full one cannot be the bundled default: 57,174 rows exceeds CCR_MAX_ROWS (50,000 on both deployments) so ingest would refuse it outright, 38 MB against a 156 KB sample_data would ride in the Docker image and the Space repo, and a demo has to finish in seconds on 2 vCP…3a1d713
-
feat(ui): show language names instead of ISO codes
PI request: "change the name of languages from acronyms to actual names (e.g., ar -> Arabic)". Display only. Codes stay exactly as they were everywhere they are recorded - run metadata, the results CSV, construct YAML, and the structured fields on warnings - because those are the reproducibility record and the output contract, so exports and reproduction scripts are byte-identical to before. app/languages.py is the single source of the mapping so the UI and the warning text cannot drift apart. Renamed at every point a code reached a human: the text-language selector, the language column in the runs table, the model list's language coverage, and the three language warnings. The registry's named sets were leaking a machine identifier too, so xlm_roberta_100 now reads "100+ languages (XLM-RoBERTa coverage)" while models.yaml keeps the checkable name. /api/languages now returns {code, name} rather than a bare code list; its one existing assertion is updated. A coverage test asserts ever…41ee593 -
feat(privacy): anonymous session scope, construct TTL, creation cap [spec 0009]
Follows spec 0008, which scoped custom constructs to signed-in owners and left the anonymous tier as one shared bucket. Digging into that surfaced that three separate defects were being read as one "constructs are public" bug. No anonymous identity: owner_user_id == "" meant anonymous, so every anonymous visitor shared a bucket and saw everyone else's projects and constructs. Anonymous visitors now carry a signed session-id cookie and own their rows as "anon:<id>", so one column carries both ownership kinds and the shared bucket is retired for projects and constructs at once. Signing in ADOPTS that session's rows into the account: without it, scoping anonymous work to a session would strand it at sign-in, and it also replaces the reason 049074a left anonymous projects open to everybody. No lifecycle: retention.py imported Corpus, Job, Project - Construct was not in it, and constructs hang off no project, so nothing ever cascaded to them. Corpora go when a run finishes and projects ex…b35131c -
fix(privacy): scope custom constructs to their creator, add delete [spec 0008]
A PhD student reported seeing custom constructs on the public site that they had not created, with no way to remove them. Construct had no owner column at all, so every custom construct anyone typed was returned to every visitor by GET /api/constructs - listed, with some irony, under a picker heading reading "My custom constructs". This is the projects listing leak (049074a) one table over: that fix taught _visible_owners the right rule, but constructs had no ownership concept to correct. Construct gains owner_user_id, mirroring Project, and the listing applies the same rule: seeds are public, a signed-in user sees only their own custom constructs, anonymous ones stay in the shared anonymous bucket. Both columns are additive with scalar defaults, so auto_migrate_sqlite adds them to the deployed databases and existing constructs become anonymous - which removes them from every signed-in user's picker on upgrade, without a manual cleanup. Deletion is a confirmation dialog in the picker…3594eb6
-
fix(retention): delete project rows in FK-safe order, enforce FKs in SQLite
delete_project_cascade removed jobs, then corpora, then the project, which is the right order to write but not the order that reached the database. Within a single flush SQLAlchemy sorts DELETEs by mapper dependency, and Job has no ORM relationship to Corpus (only a raw ForeignKey column), so it was free to emit DELETE FROM corpora first and violate jobs_corpus_id_fkey. Postgres enforces that constraint, SQLite does not unless foreign_keys is ON, so this only ever failed on a deployed instance: - DELETE /api/projects/{id} returned 500 (also the /admin delete path). - purge_expired_anonymous shares the cascade, so the hourly retention sweep raised on the first expired project that had ever been run and purged nothing after it. Anonymous projects and runs outlived CCR_ANON_TTL_HOURS; the dev database still holds one from 2026-08-06. Uploaded files were never affected, since an anonymous run's corpus is deleted when the run finishes. Fix is a flush between levels, so each level…248d767 -
fix(constructs): resolve the review's open questions from Noor's answers
She replied with the source she used for each disputed item, and every one checks out. Three of the four disagreements were about which PRINTING of a scale is authoritative, not about who read it correctly. MFQ Fairness: I was wrong to revert her. She read the questionnaire printed at the end of Graham et al. (2011), which is the paper this construct cites, and it does say "treated differently from others" (twice). The moralfoundations.org MFQ30 handout says "than others". Her correction is applied and source_url now points at the paper, so items, citation, and source finally agree. RSES and CBI work-related burnout: she used the source_url recorded on each construct, and both mappings are exactly right against it. The Rosenberg questionnaire at socy.umd.edu starts with "I feel that I'm a person of worth" and its wording and reverse keys match ours item for item, so only our ORDER was wrong. Both are reordered to the printed order with item ids renumbered, so item_id means "source it…0af19f8 -
fix(constructs): verify MFQ against the source the review sheet omitted
Checking whether the follow-up questions for the reviewer were fair turned up that two of them were not. The review sheet shipped with an EMPTY source_url for mfq_care and mfq_fairness, so the reviewer had nothing to check those against and correctly flagged it. That was our omission, not a missing document: the MFQ30 is public, all four items in each construct match it verbatim, and both are now verified with the source recorded. This also settles the "differently than/from others" disagreement in favour of the wording already stored. Also confirmed independently, so the reviewer does not need to be asked: the Subjective Happiness Scale PDF at the source_url we gave her does read "Compared to most of my peers", exactly as she corrected it. And for context on the two item-order findings: the APA measures-package printing of the RSES numbers its items exactly as we store them, while the reviewer's renumbering matches another widely-used printing. Same version-mismatch pattern as Edmo…10425ec -
fix(constructs): correct the review pass after a validity review
A research-validity review of the previous commit found four real problems. All four are fixed here. Two of the reviewer's wording corrections moved an item AWAY from the publication its construct cites, and I had applied them under a `verified` badge. Checked against the sources: the published MFQ30 reads "treated differently than others" (not "from"), and Edmondson (1999) reads "It is safe to take a risk on this team" (not "in") - the proposed "in this team" comes from a third-party questionnaire, whose URL I had also promoted over the working Edmondson PDF. Both items keep their published wording, both constructs are held back as unverified with the disagreement recorded, and the source_url override is dropped. The reviewer's item-ORDER findings for rses and cbi_work_related_burnout were silently dropped: my classifier only read notes on rows that failed one of the four checks, and these sat on rows marked OK. Order feeds item_hash and decides what sim_item_N means, so both are no…461b859 -
docs: MIT-only license, keep planning docs out of a public repo
LICENSE (MIT) was already committed; the two untracked drafts beside it were an exact duplicate and an Apache-2.0 alternative representing a decision nobody had made. Single MIT, both drafts removed, README now states it and separates the code license from the questionnaire items, which belong to their original authors. Root-level .docx files are gitignored: the deployment plan and the item generation proposal are PI correspondence that lives in the lab's Drive, and this repo is public. docs/design/*.docx stays tracked. Also corrects two stale ROADMAP entries (anchor vectors shipped on 2026-08-22; the LICENSE line still claimed the repo had none) and records the library-versioning, license, and benchmark decisions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>1b10b34 -
chore(registry): record measured CPU throughput for all seven models
Every model said expected_time_per_1k_texts_cpu: benchmark_required, not just the four PsyEmbedding ones. Measured with scripts/bench_models.py --threads 2 --quick. Throughput varies about 50x between a 15-word social post and a 250-word document, so the field is three numbers rather than one, and the comment names the host and date: a shared cloud vCPU runs roughly 2-4x slower than the machine these came from, so they need a re-run on the Space before they are shown to users. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>684411f -
feat(constructs): apply the 2026-08-25 library review [spec 0007]
Noor Skhiri reviewed all 525 items across the 94 constructs (wording, reverse keying, subscale grouping, citations). This applies it. Split by what item_hash covers. Item changes ship as a NEW version: 61 reverse-scoring flags corrected across 14 constructs (35 -> 96 flagged items), 9 verbatim wording fixes, and one SCS-SF item moved to Over-identification per the source coding key. 23 constructs gain a version 2; their version 1 files keep their original items and are marked archived so runs that pinned them still resolve. Everything outside the hash is edited in place: 88 constructs promoted to verified with reviewer and date recorded, plus citation and source_url repairs. Six constructs stay needs_verification pending two PI decisions (the IPIP "I" prefix and the K10 stem). Two loader gaps had to close for this to ship: sync_library was insert-only, so a status change could never reach an already-seeded database, and the picker listed every version, so a v2 would show the same co…4afd259
-
fix(models): drop the PsyEmbedding pooling workaround, re-pin to fixed repos
The four lab PsyEmbedding repos shipped a modules.json referencing a 1_Pooling config the repos did not contain, so SentenceTransformer(id) could not load them and the registry carried a pooling_fallback flag telling the backend to assemble Transformer+mean-Pooling by hand. The missing config was pushed upstream on 2026-08-22, so the workaround is no longer needed. - models.yaml: re-pin all four models to the fixed revision (one commit above the old pin) and remove pooling_fallback. - registry test inverted: psyembedding models must now report pooling_fallback is None, so the workaround cannot creep back in. No code change is required. Both branch points (ccr.py model loading and reproducibility.py script generation) key off pooling_fallback being truthy, so new runs take the plain one-line loader automatically, while older runs keep the explicit module assembly recorded in their own metadata. Verified against the Hub: all four pinned revisions exist and carry 1_Pooling/config.j…597c9b7
-
feat(scoring): anchor vectors (bipolar constructs) [spec 0006]
Score texts along the axis between a target construct and a contrasting "opposite" construct: AV = target_centroid - opposite_centroid, anchor score = cosine(text, AV) (default) or dot(text, AV). Because embeddings are L2-normalized, a pole's CCR score (mean of per-item cosines) equals text . centroid, so the dot score is exactly target_score - opposite_score and the export is self-checking. - engine: run_ccr_anchored() (per-pole sims/scores + anchor score + metadata), reuses the doc-embedding cache; degenerate poles flagged. - data model: Job.opposite_construct_id + similarity_metric (additive columns). - runner: anchored export columns under output_schema_version 1.2, bipolar summary, both construct snapshots + scoring block in metadata, ANCHOR_DEGENERATE_POLES warning. - API: POST /api/jobs opposite_construct_id + similarity_metric, validated (single target, distinct poles, cosine|dot); JobOut echoes anchored fields. - reproduction script: anchored variant embeds both item …f341328
-
feat(guide): add a sample-datasets section to the public guide
Links the four ready-to-run example corpora (served at /samples) so new users can try CCR without uploading their own data, with a pointer from the upload step. Suggested by Meriel Burnett. CHANGELOG also records the footer and project-privacy fixes.c993be8 -
fix(privacy): stop showing shared anonymous projects to signed-in users
_visible_owners() returned ("", user_id) for a signed-in user, so every signed-in account saw the shared anonymous bucket (owner_user_id == ""): projects anyone created without signing in. Signed-in users now see only their own projects. Anonymous projects stay open by direct access so the anonymous -> sign-in continuity flow keeps working; they just no longer appear in a signed-in user's project list. Adds a regression test.049074a -
fix(ui): show the site identity footer on the welcome view only
The footer rendered on every view. It now renders only on the public welcome/landing page, so the dashboard and results views are uncluttered. Site identity for scanners already lives in the served HTML (da069b3).59b268b
-
fix(site): put lab identity into the served HTML, not just the JS bundle
The SPA served a 506-byte shell: a title, a script tag, and an empty root div. Every word identifying the lab lived in WelcomePage.jsx, which a crawler never executes. A scanner therefore saw a newly registered domain offering Google sign-in and file upload with no statement of who runs it, which reads as a deceptive-site fingerprint and contributed to a Safe Browsing flag. - frontend/index.html: meta description, Open Graph tags, and a <noscript> block naming the lab, the institution, the PI, contact, and source repo. - App.jsx: a site footer on every page carrying the same identity. - styles.css: .site-footer, plain text by design so it stays readable to clients that do not run the bundle. Served HTML goes from 506 to 2026 bytes.da069b3 -
chore: remove stale git lock files committed by mistake
Two empty .git/index.lock remnants were left in _to_delete/ by a bridge session that could not unlink on the mounted folder, then swept into the previous commit. They are zero-byte artifacts, not repo content.03b0147 -
fix(auth): trim whitespace from CCR_APP_URL to prevent sign-in issues
docs: update deployment instructions and clarify CCR_APP_URL usage test: add test for app_url to handle pasted whitespace in CCR_APP_URL chore: update reverse proxy worker with correct upstream host3fde616
-
chore: add MIT LICENSE and production deploy assets
- LICENSE: MIT (Culture and Morality Lab, UMass Amherst). The site states the tool is free and open source, so the repo now carries a matching license. MIT is the standard for academic research software and maximizes reuse and citation. - deploy/reverse-proxy-worker.js: Cloudflare Worker that serves the Hugging Face Space at the lab's custom domain (Spaces have no native custom-domain support), keeping the domain in the address bar. - deploy/PRODUCTION_RUNBOOK.md: full steps to move to lab-owned accounts (Supabase, R2, HF Space, Groq), migrate data, and wire the domain. - .gitignore: exclude personal .agents/ and .codex/ tooling.4d1197e
-
fix(ui): keep the AI drafting tooltip text inside its box
The info bubble inherited the base `button { white-space: nowrap }` rule, so its longest line ("Not a validated scale...") overflowed the 300px box instead of wrapping. Reset white-space (plus overflow-wrap) on .infotip-pop. Also switched the trigger from a <button> to a focusable <span>: the bubble nests a list and a link, which are invalid inside a button and made the "More in the guide" link unreliable. aria-label carries the full text for screen readers; the visual bubble is aria-hidden with its link out of the tab order.e8f94ba -
feat(guide): public how-to guide + live AI model details
Split the guides so the public gets a plain how-to page and the lab keeps its click-through testing guide: - /guide now serves a new public how-to guide (public_guide.html): the upload -> construct -> run -> export flow, the four ways to add a construct (library, typed, uploaded, AI drafting), a models table, and a reproducibility section covering the results CSV, run metadata, and the offline reproduction script. - /testing serves the original testing guide, gated to lab and above (same gate as /product); the 403 explainer points to the public guide. Header shows a lab-only Testing link. - Landing page links the PI's name (Mohammad Atari) to mohammadatari.com. - AI construct path shows "Drafted by <model>" with a hover/focus info bubble (model, provider, prompt version, item rules, daily cap). Model identity is read live from the API: new item_generation.public_info() surfaced as a `generation` block on /api/auth/me, so the tooltip and guide never hardcode a model that…8586c1b
-
merge: unify lab-org repo history with the deploy repo
The lab repo (Culture-and-Morality-Lab/ccr-platform) and this deploy repo evolved as parallel histories of the same project. This merge makes them one: current code wins every conflicting path; the lab repo's unique content is adopted - design doc, specs 0001-0005, DECISIONS.md, CLAUDE.md, AGENTS.md, evals, and .claude agent tooling (files our code comments have been referencing all along). Stale built assets from the old tree dropped. From here the lab org repo is the canonical remote; identical history is kept on all remotes.5619d79
-
ui: redesign the new-construct form as a contained panel with source cards
Tester feedback: the form read as cluttered - two levels of the same choice (entry buttons + tabs), every path's fields visible at once, and a flex-wrap bug stretching the Close button full-width. - One '+ New construct' entry; inside, three self-describing radio-cards (Type or paste / Upload a file / Draft with AI) - Only the chosen path's fields render, in task order; Reference hidden on the AI path, Description shown only there (required, drafts from it) - AI path: items box + Save appear once there's a draft to review; quota sits inline next to the Draft button - Panel is a bordered sub-card with its own header + close069d95d -
feat: audit trail for AI drafts + disabled-button reasons everywhere
- ConstructGeneration.items stores the ORIGINAL drafted items (PI request 2026-08-07): run metadata now shows the as-drafted text alongside the researcher's saved/edited items - Draft-items button disabled until Name + Description exist, reason inline (same pattern as Save; errors in the top banner go unseen) - Run button gets a 'To run, still needed: ...' line when disabled - AI generation quota shown from tab-open via auth/me, not only after the first draftc49bb09 -
ui: three explicit construct paths + Save gating + visible AI drafts
PI test feedback (2026-08-07): - Step 2 hint enumerates the three acquisition paths; two buttons next to the picker ('+ Your own items', 'Draft with AI') replace the generic '+ Custom construct' - Save construct is disabled until name + items exist, with an inline line saying what's missing (the silent dead-click read as broken - the error banner sits at the top of the page, out of view) - After drafting, scroll/focus the items box and say 'N items drafted - they're in the box below' (drafts landed off-screen unnoticed) - /guide and MANUAL_TESTING.md updated to the new button namesdc9da41
-
docs: DEPLOY.md - persistence caveat resolved (Postgres + R2 live, verified)
5e68150 -
docs: AI item-drafting scenarios in /guide and MANUAL_TESTING.md
Click-through test steps for the Draft-with-AI tab (happy path, review labeling, library guardrail, vague input, provenance in metadata, daily cap, signed-out/no-key behavior) + the 20/day row in the limits table.bf6c4d6 -
ui: item-source chooser in custom-construct form (type / upload / AI)
The flat form stacked all three item sources at once and read as clutter (tester feedback); now a segmented chooser shows one source's controls at a time, with the items textarea below as the shared review surface. Also fixes the Draft-items button baseline (field-row aligns controls).6e3ac9f