Skip to content
Closed
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
2 changes: 1 addition & 1 deletion Projects/MathlibDemo/leanweb-config.json
Comment thread
joneugster marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "Latest Mathlib",
"name": "_LeanVers_ with Mathlib",
"default": true,
"hidden": false,
"examples": [
Expand Down
2 changes: 1 addition & 1 deletion Projects/Stable/lean-toolchain

@joneugster joneugster May 28, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's these day the recommended way of automatically follow stable releases? the file leanweb-build.sh contains lake update -R this will not be good enough to automatically update to v4.30.0, v4.31.0, ... when they are released, will it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For live.lean-lang.org, we're currently getting the stable release by following the cslib stable tag: we literally

curl -L https://raw.githubusercontent.com/leanprover/cslib/stable/lean-toolchain | tee lean-toolchain

But this doesn't work for "latest release" where there's no corresponding tag in mathlib or cslib, and it doesn't work as soon as you want to say "I want the latest stable release where there's a version of cslib, and lean4export, and comparator"... which is also something I want. So I'm planning on doing something a bit more complicated, I can try upstreaming if it would be helpful because there's not currently a great answer here.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok that sounds even hackier indeed.

I'm going to revert that for this PR, but I'd be happy to have a look at that in another PR seperately.

Ideally lake would provide an official way for projects to update to the latest stable Lean release, something like lake update --stable. Currently, to my best knowledge, the "most official" way to follow stable Lean is setting the toolchain to stable. Therefore, I would like to keep that in this demo project.

Original file line number Diff line number Diff line change
@@ -1 +1 @@
leanprover/lean4:stable
leanprover/lean4:v4.29.0
2 changes: 1 addition & 1 deletion Projects/Stable/leanweb-config.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"name": "Stable Lean",
"name": "_LeanVers_",
"hidden": false
}
2 changes: 2 additions & 0 deletions client/src/api/project-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export type LeanWebProjectConfig = {
hidden: boolean
/** The default project. There must be exactly one project marked as default */
default: boolean
/** Sort order: the default project always comes first, followed by projects in decreasing sort order. (No sort order is treated as 0.) */
sortOrder: number
/** A list of examples which are added under the menu `Examples` */
examples: LeanWebExample[]
}
Expand Down
11 changes: 10 additions & 1 deletion client/src/store/project-atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,19 @@ const projectsQueryAtom = atomWithQuery<LeanWebProject[]>(() => ({
},
}))

/** Sort alphabetically while the `default` project always comes first */
/**
* Sort alphabetically while the `default` project always comes first, then
* order by descending sortOrder (nullish treated as 0), then order by name.
*/
function sortProjects(p: LeanWebProject, q: LeanWebProject): number {
if (p.config.default) return -1
if (q.config.default) return 1

// Secondary sort: sortOrder field
const n = q.config.sortOrder - p.config.sortOrder
if (n !== 0) return n

// Fallback: names
return p.config.name.localeCompare(q.config.name)
}

Expand Down
2 changes: 1 addition & 1 deletion cypress/e2e/encoding.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe("", () => {
cy.get("div.view-lines").type(payload);

// change project
cy.get("nav>*>select[name='leanVersion']").select("Stable Lean");
cy.get("nav>*>select[name='leanVersion']").select("Stable");
cy.url().should("include", "project=Stable");

cy.contains("div.view-line", payload).should("exist");
Expand Down
16 changes: 16 additions & 0 deletions cypress/e2e/settings.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,19 @@ describe("The Settings can be changed for", () => {
});
});
});

describe("The version selection menu", () => {
it("displays a versioned name for the MathlibDemo project", () => {
cy.visit("/");
cy.get("nav>*>select[name='leanVersion'] option[value='MathlibDemo']")
.invoke("text")
.should("match", /^Lean v4\.[0-9]+\.[0-9]+(-rc[0-9]+)? with Mathlib/);
});

it("displays a versioned name for the Stable project", () => {
cy.visit("/");
cy.get("nav>*>select[name='leanVersion'] option[value='Stable']")
.invoke("text")
.should("match", /^Lean v4\./);
});
});
6 changes: 5 additions & 1 deletion cypress/e2e/spec.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ describe("The Editor", () => {
cy.iframe().contains("Stable.lean").should("exist");
cy.get(".dropdown>.nav-link>.fa-bars").click();
cy.contains(".nav-link", "Lean Info").click();
cy.containsAll(".modal", ["leanprover/lean4:stable", "(no dependencies)"])
cy.containsAll(".modal", [
"Stable",
"leanprover/lean4:",
"(no dependencies)",
])
.find(".modal-close")
.click();
});
Expand Down
15 changes: 13 additions & 2 deletions doc/Projects.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,30 @@ The file `leanweb-config.json` takes the following form:
"name": "Display name",
"default": false,
"hidden": false,
"sortOrder": 0,
"examples": [
{ "file": "MathlibDemo/Bijection.lean", "name": "Example's display name" },
...
]
}
```

- `name`: The display name of the project as shown in the dropdown menu
- `name`: The display name of the project as shown in the dropdown menu. The substrings `_Vers_` and
`_LeanVers_` in the project name will be replaced by indicators of which version is being used:
- If the toolchain is in a recognized format (a regular release like `v4.30.0-rc1` or nightly
release like `2026-04-04`), then `_Vers_` will be replaced by the version number (e.g.
`v4.30.0-rc1`) and `_LeanVers_` will be replaced by "Lean" followed by the version number (e.g.
`Lean v4.30.0-rc1`)
- If the toolchain is not in a recognized format, both `_Vers_` and `_LeanVers_` will be replaced
by "Lean".
- `default`: There must be exactly one project with this set to `true`. This is the project loaded
by default and when no project is specified in the url.
- `hidden`: If set to `true`, then the project does not appear in the dropdown and can
only be accessed via direct link.
- `examples`: list of examples. The path is relativ to the project's directory, e.g. `{PROJECTS_BASE_PATH}/{PROJECT_FOLDER}/{EXAMPLE_PATH.lean}`
- `sortOrder`(non-negative number): sort order of the projects in the dropdown. The default always
comes first, then projects with higher `sortOrder` value. Ties are resolved alphabetically.
- `examples`: list of examples. The path is relativ to the project's directory, e.g.
`{PROJECTS_BASE_PATH}/{PROJECT_FOLDER}/{EXAMPLE_PATH.lean}`

## automatic builds

Expand Down
9 changes: 5 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 29 additions & 5 deletions server/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import * as jsonrpcserver from "vscode-ws-jsonrpc/server";
import { WebSocketServer } from "ws";

import { zLeanWebProjectConfig } from "./types.mjs";

let socketCounter = 0;

function logStats() {
Expand Down Expand Up @@ -59,11 +61,24 @@

const projectDir = path.join(PROJECTS_BASE_PATH, entry.name);
const configPath = path.join(projectDir, "leanweb-config.json");
const toolchainPath = path.join(projectDir, "lean-toolchain");

let config = null;
try {
const raw = await fs.promises.readFile(configPath, "utf-8");
config = JSON.parse(raw);
const toolchain = (
await fs.promises.readFile(toolchainPath, "utf-8")
).trim();

config = zLeanWebProjectConfig.parse(JSON.parse(raw));
config.name = config.name.replaceAll(
"_LeanVers_",
toolchainToName(toolchain, true),
);
config.name = config.name.replaceAll(
"_Vers_",
toolchainToName(toolchain, false),
);
} catch (err) {
console.debug(err);
// File missing or invalid JSON — keep config as null
Expand All @@ -73,10 +88,11 @@
projects.push({
folder: entry.name,
config: {
name: String(config.name), // TODO: ensure this is not null
hidden: Boolean(config.hidden) ?? false,
default: Boolean(config.default) ?? false,
examples: config.examples ?? [], // TODO: validate
name: config.name,
hidden: config.hidden ?? false,
default: config.default ?? false,
examples: config.examples ?? [],
sortOrder: config.sortOrder ?? 0,
},
});
}
Expand Down Expand Up @@ -255,7 +271,7 @@
}
try {
conn.send(JSON.stringify(message));
} catch (e) {

Check warning on line 274 in server/index.mjs

View workflow job for this annotation

GitHub Actions / Lint

'e' is defined but never used

Check warning on line 274 in server/index.mjs

View workflow job for this annotation

GitHub Actions / Lint

'e' is defined but never used
conn.close();
}
};
Expand All @@ -277,7 +293,7 @@
pongReceived = false;
try {
conn.ping();
} catch (e) {

Check warning on line 296 in server/index.mjs

View workflow job for this annotation

GitHub Actions / Lint

'e' is defined but never used

Check warning on line 296 in server/index.mjs

View workflow job for this annotation

GitHub Actions / Lint

'e' is defined but never used
conn.close();
}
}
Expand Down Expand Up @@ -455,3 +471,11 @@
const test = cp.spawnSync("bwrap", ["--version"], { stdio: "ignore" });
return test.status === 0;
}

function toolchainToName(toolchain, prefixLean) {
const nightly = toolchain.match(/^leanprover\/lean4\:nightly-(.*)$/);
if (nightly) return prefixLean ? `Lean ${nightly[1]}` : nightly[1];
Comment thread
joneugster marked this conversation as resolved.
const release = toolchain.match(/^leanprover\/lean4\:(.*)$/);
if (release) return prefixLean ? `Lean ${release[1]}` : release[1];
return "Lean";
}
3 changes: 2 additions & 1 deletion server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"ip-anonymize": "^0.1.0",
"memfs": "^4.51.1",
"nocache": "^4.0.0",
"ws": "^8.18.3"
"ws": "^8.18.3",
"zod": "^4.4.3"
},
"devDependencies": {
"@types/node": "^24.10.1",
Expand Down
14 changes: 14 additions & 0 deletions server/types.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { z } from "zod";

export const zLeanWebExample = z.object({
file: z.string(),
name: z.string(),
});

export const zLeanWebProjectConfig = z.object({
name: z.string(),
hidden: z.boolean().optional(),
default: z.boolean().optional(),
sortOrder: z.number().min(0).optional(),
examples: z.array(zLeanWebExample).optional(),
});
Loading