Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
738dbac7fe | ||
|
|
98f93f5ad5 | ||
|
|
4049809aa4 |
@@ -4,6 +4,11 @@ One line per change, grouped by the release that shipped it. Newest
|
|||||||
first. A release is a Cargo.toml bump plus a `v<version>` tag; the
|
first. A release is a Cargo.toml bump plus a `v<version>` tag; the
|
||||||
content repos pin the tag they run (see the end of this file).
|
content repos pin the tag they run (see the end of this file).
|
||||||
|
|
||||||
|
## 0.6.16 (2026-10-01)
|
||||||
|
|
||||||
|
- The lint refuses an alternative that has a form to send (fields to fill in, an invite, or a bucket to record into) and no `action`. The button is only drawn for an alternative with an `action`, so such a form could be filled in and never sent, which is what happened to uhhm.no's first invite form. An alternative that moves its own record (`self_transition`) draws its own button and is left alone.
|
||||||
|
- `portal iris` is the lint's name, after the iris that keeps bad content out of the portal. `portal lint` is the same command and keeps working.
|
||||||
|
|
||||||
## 0.6.15 (2026-10-01)
|
## 0.6.15 (2026-10-01)
|
||||||
|
|
||||||
- An invite may carry its own mail: `invite: { group, mail: { subject, body } }`. Every field of the form is a placeholder besides `{name}`, `{link}`, `{expires}` and the rest, and a date and time (`type: datetime-local`) is written out in words ("tirsdag 6. oktober kl. 15:00"). The words and the form are kept with the invite, so sending it again sends the same mail. This is for a desk that invites someone to a conversation at an hour, not into a group for good.
|
- An invite may carry its own mail: `invite: { group, mail: { subject, body } }`. Every field of the form is a placeholder besides `{name}`, `{link}`, `{expires}` and the rest, and a date and time (`type: datetime-local`) is written out in words ("tirsdag 6. oktober kl. 15:00"). The words and the form are kept with the invite, so sending it again sends the same mail. This is for a desk that invites someone to a conversation at an hour, not into a group for good.
|
||||||
|
|||||||
+5
-1
@@ -4,11 +4,15 @@ The iris over a site. A content push is an incoming wormhole; nothing
|
|||||||
comes through until it checks out.
|
comes through until it checks out.
|
||||||
|
|
||||||
```
|
```
|
||||||
portal lint --path questions # a local checkout
|
portal iris --path questions # a local checkout
|
||||||
portal lint --repo https://project.uhhm.no/uhhm/questions
|
portal lint --repo https://project.uhhm.no/uhhm/questions
|
||||||
portal lint replay --path questions --nats nats://127.0.0.1:14222
|
portal lint replay --path questions --nats nats://127.0.0.1:14222
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`portal iris` is the lint's name, after the iris that keeps bad content
|
||||||
|
out of the portal; `portal lint` runs the same thing, so workflows that
|
||||||
|
already call it keep working.
|
||||||
|
|
||||||
It was its own binary, iris, until portal 0.6: the same code, released
|
It was its own binary, iris, until portal 0.6: the same code, released
|
||||||
as portal's twin. Now it is the `portal` binary's `lint` command, so a
|
as portal's twin. Now it is the `portal` binary's `lint` command, so a
|
||||||
content repo's CI downloads the release and runs it:
|
content repo's CI downloads the release and runs it:
|
||||||
|
|||||||
+6
-3
@@ -1323,6 +1323,7 @@ alternatives:
|
|||||||
public: true
|
public: true
|
||||||
source: {{ kind: kv, bucket: things }}
|
source: {{ kind: kv, bucket: things }}
|
||||||
- name: Object
|
- name: Object
|
||||||
|
action: /object
|
||||||
record_as: things
|
record_as: things
|
||||||
features:
|
features:
|
||||||
- name: Yours
|
- name: Yours
|
||||||
@@ -1420,6 +1421,7 @@ id: /t
|
|||||||
name: Where is it?
|
name: Where is it?
|
||||||
alternatives:
|
alternatives:
|
||||||
- name: A
|
- name: A
|
||||||
|
action: /t
|
||||||
features:
|
features:
|
||||||
- name: Where
|
- name: Where
|
||||||
requirements:
|
requirements:
|
||||||
@@ -1523,6 +1525,7 @@ id: /b
|
|||||||
name: B
|
name: B
|
||||||
alternatives:
|
alternatives:
|
||||||
- name: A
|
- name: A
|
||||||
|
action: /b
|
||||||
features:
|
features:
|
||||||
- name: ""
|
- name: ""
|
||||||
requirements:
|
requirements:
|
||||||
@@ -1564,7 +1567,7 @@ alternatives:
|
|||||||
|
|
||||||
fn question_with_requirement(req_yaml: &str) -> std::collections::HashMap<String, Question> {
|
fn question_with_requirement(req_yaml: &str) -> std::collections::HashMap<String, Question> {
|
||||||
let question: Question = serde_yaml::from_str(&format!(
|
let question: Question = serde_yaml::from_str(&format!(
|
||||||
"id: /g\nname: G\nalternatives:\n - name: A\n features:\n - name: \"\"\n requirements:\n{req_yaml}\n"
|
"id: /g\nname: G\nalternatives:\n - name: A\n action: /g\n features:\n - name: \"\"\n requirements:\n{req_yaml}\n"
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
std::collections::HashMap::from([(question.id.clone(), question)])
|
std::collections::HashMap::from([(question.id.clone(), question)])
|
||||||
@@ -1835,7 +1838,7 @@ alternatives:
|
|||||||
fn recorded_buckets_must_be_attended() {
|
fn recorded_buckets_must_be_attended() {
|
||||||
let write_only = vec![(
|
let write_only = vec![(
|
||||||
"index.yaml".to_string(),
|
"index.yaml".to_string(),
|
||||||
"name: Home\nalternatives:\n - name: send\n record_as: black_hole\n".to_string(),
|
"name: Home\nalternatives:\n - name: send\n action: /\n record_as: black_hole\n".to_string(),
|
||||||
)];
|
)];
|
||||||
let questions = build_questions(&write_only).unwrap();
|
let questions = build_questions(&write_only).unwrap();
|
||||||
let err = validate_questions(&questions, &Default::default()).unwrap_err();
|
let err = validate_questions(&questions, &Default::default()).unwrap_err();
|
||||||
@@ -1846,7 +1849,7 @@ alternatives:
|
|||||||
let with_desk = vec![
|
let with_desk = vec![
|
||||||
(
|
(
|
||||||
"index.yaml".to_string(),
|
"index.yaml".to_string(),
|
||||||
"name: Home\nalternatives:\n - name: send\n record_as: inbox\n".to_string(),
|
"name: Home\nalternatives:\n - name: send\n action: /\n record_as: inbox\n".to_string(),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"review/index.yaml".to_string(),
|
"review/index.yaml".to_string(),
|
||||||
|
|||||||
@@ -512,6 +512,20 @@ pub fn validate_questions(
|
|||||||
}
|
}
|
||||||
for question in questions.values() {
|
for question in questions.values() {
|
||||||
for alternative in &question.alternatives {
|
for alternative in &question.alternatives {
|
||||||
|
// A form is sent with its button, and the button is only
|
||||||
|
// drawn for an alternative that says where to go afterwards:
|
||||||
|
// without `action` the person fills it in and cannot send it.
|
||||||
|
let asks = alternative.features.iter().any(|f| f.requirements.iter().any(|r| r.kind != "hidden"));
|
||||||
|
if (asks || alternative.invite.is_some() || alternative.record_as.is_some())
|
||||||
|
&& alternative.action.is_none()
|
||||||
|
&& alternative.self_transition.is_none()
|
||||||
|
{
|
||||||
|
anyhow::bail!(
|
||||||
|
"question {:?} alternative {:?}: it has a form to send but no `action`, so no button is drawn; give it `action:`, the page to go to once it is sent (its own page is fine)",
|
||||||
|
question.id,
|
||||||
|
alternative.name
|
||||||
|
);
|
||||||
|
}
|
||||||
if alternative.one_per_person && alternative.record_as.is_none() {
|
if alternative.one_per_person && alternative.record_as.is_none() {
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
"question {:?} alternative {:?}: one_per_person keeps each person's own record, and there is no record_as to keep it in",
|
"question {:?} alternative {:?}: one_per_person keeps each person's own record, and there is no record_as to keep it in",
|
||||||
|
|||||||
+4
-3
@@ -1,5 +1,6 @@
|
|||||||
/// One binary, three jobs: `portal` (or `portal serve`) runs a site;
|
/// One binary, three jobs: `portal` (or `portal serve`) runs a site;
|
||||||
/// `portal lint` checks a content repo the way the site reads it;
|
/// `portal iris` checks a content repo the way the site reads it (the
|
||||||
|
/// iris keeps bad content out of the portal; `portal lint` is the same);
|
||||||
/// `portal plan`, `build`, `report` and the rest train one. The lint
|
/// `portal plan`, `build`, `report` and the rest train one. The lint
|
||||||
/// and the trainer are the same code as the server, at the same
|
/// and the trainer are the same code as the server, at the same
|
||||||
/// version, so they never disagree about what content means.
|
/// version, so they never disagree about what content means.
|
||||||
@@ -8,7 +9,7 @@
|
|||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
let argv: Vec<String> = std::env::args().skip(1).collect();
|
let argv: Vec<String> = std::env::args().skip(1).collect();
|
||||||
match argv.first().map(String::as_str) {
|
match argv.first().map(String::as_str) {
|
||||||
Some("lint") => return portal::lint::run(argv[1..].to_vec()).await,
|
Some("iris" | "lint") => return portal::lint::run(argv[1..].to_vec()).await,
|
||||||
Some("plan" | "build" | "answer" | "rate" | "prefer" | "score" | "simulate" | "compare" | "report" | "generate") => {
|
Some("plan" | "build" | "answer" | "rate" | "prefer" | "score" | "simulate" | "compare" | "report" | "generate") => {
|
||||||
// The trainer runs models through shell commands and waits
|
// The trainer runs models through shell commands and waits
|
||||||
// on them; off the runtime's threads, as a CLI should.
|
// on them; off the runtime's threads, as a CLI should.
|
||||||
@@ -19,7 +20,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
Some("serve") | None => {}
|
Some("serve") | None => {}
|
||||||
Some(other) => anyhow::bail!("portal: unknown command {other:?}; one of serve, lint, plan, build, answer, rate, prefer, score, simulate, compare, report, generate"),
|
Some(other) => anyhow::bail!("portal: unknown command {other:?}; one of serve, iris (or lint), plan, build, answer, rate, prefer, score, simulate, compare, report, generate"),
|
||||||
}
|
}
|
||||||
serve().await
|
serve().await
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user