I just shipped the first part of this: the primary parent collection badge is now shown in the notebook header. I’m also hoping to ship some ability to quickly navigate to other notebooks in the same collection (likely as a collection panel), and perhaps a better badge when a notebook is in multiple collections. Thanks for the feedback!
I understand. Thanks for your answers. One more question
Will it be possible to use the web interface as an IDE for Observable Desktop to edit local files? Or do you plan to develop a dedicated IDE?
Rather than spam this thread, as I’ve been playing with the Notebook 2.0 web interface, I’ve been making notes on the good bits, the bugs/idiosyncrasies and feature requests that occur to me. Some of these are perhaps my ignorance, some might be ones you are already aware of / working on, and some might be useful feedback for the Observable team.
I will likely continue to add to this, but will keep existing bullet names for easier reference.
Great to see this coming together!
I don’t think so. Not directly or synchronously or soon. There should probably be some portability eventually, but it’s hard to bridge the world of files and the world of the web without sacrificing many of the distinctive strengths of one or the other. I mean, it’s doable, it’s just probably not the highest-impact thing we can do right now. But always happy to hear about specific use cases or points of friction that make you yearn for something web-ish on desktop or desktop-ish on the web!
This is incredibly helpful!!! I’ve starred it; we’ll keep an eye on it. Nice letter-number keys too.
Briefly (since comments pane doesn’t fully work yet) — B1 and B2 are imminent, just didn’t quite make it in for “soft launch”. B3, yeah, we’ve enabled typographic replacements in markdown-it but it may have to be a setting. B4, agree but it’s tricky; I like thinking critically about expressions vs. statements but I too really miss implicit inspection of every variable when I use vanilla JavaScript. N1 we were just talking about too. N2 is very “literate programming”; I think we’re unlikely to do it because ultimately JavaScript is the true lingua franca, but note that you can option-click the “+” inserter button to get a Markdown cell.
Of course you should never worry about this!! If you’re missing something, then it tells us it’s not very clear. If we’ve thought of it, then it’s a useful vote for prioritization. If we haven’t thought of it, then so much the better. Can’t go wrong! ![]()
Not sure I fully understand the question, but we see the web and desktop editors as serving two different work styles and likely different audiences. The web editor is designed to provide an integrated, collaborative workspace on the Observable platform, whereas the desktop editor is designed to enable a self-hosted, file-based workflow (typically using git for source control). Our desktop editor is even designed to work offline, though there’s remaining work to do to have local copies of npm libraries that are commonly used in notebooks.
The web and desktop editors share the same codebase, and share IDE-like functionality such as the forthcoming dataflow panel (a.k.a. minimap). The main difference is that the web editor integrates with the rest of the Observable platform, naturally providing more functionality, whereas the desktop editor is a standalone application designed for working with local files. We will work on improving interoperability using the Notebook Kit file format, for example uploading and downloading notebooks to move between web-based and local-first development, but since the workflows are so different they aren’t intended to be interchangeable.
This feedback is invaluable! Thank you @jwoLondon. To add to Toph’s comments:
B5 - The Shift-Command-I keyboard shortcut comes from Zed, so you can blame them.
This should be a user setting, perhaps. I agree the mail pop-up is annoying so we should likely suppress the default behavior even if the Prettier action doesn’t apply.
N1 - The intent is for hidden (and unpinned) cells to be hidden completely in the reader view, but so far we’ve only implemented this in Notebook Kit when you build a static site. We should be able to fix the editor to hide hidden cells completely, it’s just a little tricky to implement due to how cells interact with each other to affect the layout.
N2 - I’m hoping we can get by with option-click… as well as option-enter from cell selection mode, option-enter to split a Markdown cell, or option-command-enter or shift-option-command-enter from another cell. There are lots of ways to insert Markdown!
We greatly appreciate all the feedback! Please keep it coming. ![]()
That’s perfect — it completely answers my questions. The ability to import and export notebooks would be absolutely fantastic. Do you have any idea when a Windows or Linux version of Observable Desktop might become available?
And once again, a huge thank you for all this work. It’s awesome. The direction you’re taking with Observable Desktop and Notebook Kit looks really promising,
It’s so snappy! Also my own code seems to run faster too? My seam carving notebook seems to have a higher frame rate for example:
https://observablehq.com/@jobleonard/seam-carving-with-forward-energy-but-we-make-a-new-image-with
Although I’m not entirely sure if that’s due to the max-width being smaller (resulting in fewer pixels being brute forced), or if there’s such a different approach in running the user’s JS that the latter is much easier to optimize for the browser, or if it’s actually the notebook DOM updates being faster (or a combination of the three).
The code folding is such a nice addition, by the way. And the new color scheme for the editor cells is much easier to read for my type of color vision deficiency (protanomaly)
Outside of the notebooks themselves there is one issue I’ve been having with the design of the observable website itself: because https://observablehq.com/ automatically redirects to my logged in user page, all of these links in the “Resources” part of the landing page that we see when not logged in are very difficult to discover:
In order to find these, I have to click “About” in a tiny link in the footer, then open the “Resources” tab on the website, and then we see these things. That’s not exactly discoverable.
It feels a bit weird to hide those, especially since they would be very valuable for new users after logging in the first time. Opening https://new.observablehq.com/ shows me a different interface for my logged in user page, but still hides these.
I’ve added a few more items (B6, N10, N11, N12). One minor breaking change is B6 - handling of escaped characters.
I am also seeing some changes in behaviour over the last couple of days. For example, intra-document hyperlinks have stopped working. Hovering over a heading hyperlink shows the correct URL # anchor, but clicking on it now leads to a grey browser entry (Chrome/Edge) or has no effect (Safari). Is there any record of timestamped release versions we can refer to when documenting issues?
I cannot see a way of dragging and dropping cells to reorder them
Yup, no drag-and-drop yet. You can click the ↑/↓ arrow buttons in the cell toolbar (bottom left), either while typing in one editor or with several cells selected. Or, from the keyboard, press Esc to enter selection mode and then Opt-↑ and Opt-↓. See Notebooks 2.0 user guide.
Yup. To expand on where this is coming from — strictly speaking, I think it’s just a difference between the old html tagged template literal and the newer htl.html, which has been available and recommended in the old editor for a couple years. So you can see the same difference within an old notebook. The new editor defaults to the 2026 standard library, which exposes the htl.html version as html.
I think that this behavior actually hasn’t changed in the last couple of days; it’s just very flaky and sensitive! Fil is working on making it more reliable right now.
We don’t have an official changelog yet, but should soon.
Keep it coming! Great feedback.
![]()
You all should try the new files panel because being able to replace and rename files is probably my favorite new feature. ![]()
Oooh, nice! Ending up with files with increased “version numbers” when I just wanted to fix a mistake in uploaded test images was a bit of an annoyance before, this feels much nicer! (and I didn’t even use the file feature that much to begin with, since I’m more focused on tinkering with algorithms than visualizing large data sets, so I can imagine it must have been worse for the people who actually do the latter)
BTW, is some form of the mini-map going to come back? Or am I finally forced to have some discipline in how I structure my notebooks instead of turning them into big balls of cell dependency spaghetti? (That would make @mootari sad, he’s using my worst ones to stress test some of his observable debugging tool notebooks, lol)
I found these two more things while writing Observable Notebooks 2.0 “idiomatic scoping” quick tests | Job van der Zwan | Observable, I think neither is quite right behavior.
Creating two variables with the same name doesn’t trigger an error until those variables are referred to
Before Observable would immediately give an error when defining two cells with the same name. Now if I create two top-level variables with the same name in different cells, it doesn’t trigger an error until their name is referenced by other code:
cellname = 0;
cellname = 1
// This cell will report an error, but the above
// two cells still won't report any issues.
cellname
Since we don’t seem have all the previous functionality for jumping to referenced cells yet this might end up with lots of wasted time searching for the duplicate top-level variable names.
Also, this might create issues for the planned future static export functionality: imagine the third cell wasn’t there. The live notebook seems to work just fine. Someone exports it, and then the javascript parts don’t run because it errors out on two top-level constants with the same name. Could be a real pain to debug.
(I guess this wasn’t a thing with the Notebook Kit before: working in one single file means that any half-decent IDE would immediately would complain about duplicate top-level function and/or variable names)
Cell order of evaluation seems to be 1: dependency, 2: last edited, 3: lexical order. I’m worried this can cause race condition-related bugs for code with side-effects (which seems to be much easier to write than before).
So this accidentally came up when I started playing with putting display inside a function that is then called from another cell. Doing so apparently just appends the passed value again to the original cell’s output. So if I have this in one cell:
const scope = fn => display(fn())
And then create three cells like so:
scope(() => "test scoped display 1")
scope(() => "test scoped display 2")
scope(() => "test scoped display 3")
… what happens is that the cell where scope was defined will display:
"test scoped display 1"
"test scoped display 2"
"test scoped display 3"
Assuming I evaluate each of those three cells exactly once. If I edit and evaluate multiple times, the scope cell happily keeps appending the result.
So that’s an interesting change: the 2.0 notebooks seem to be a lot more “mutable” compared to the rigid reactivity of the old notebooks (maybe this was already known but I hadn’t tried out Notebook Kit yet).
Sure, before I could do these kinds of things too, but it required manually peeking under the hood and bypassing the reactivity model. Avoiding it even, to prevent weird bugs from manifesting.
Anyway, aside from that the output makes sense so far, right? But if I now change something in my definition for scope, it will re-evaluate those three dependent cells. Can you guess in which order? It’s this:
"test scoped display 3"
"test scoped display 2"
"test scoped display 1"
It got reversed! And if I refresh the page, it shows what you’d expect again:
"test scoped display 1"
"test scoped display 2"
"test scoped display 3"
Here’s what I think happened: there is some internal array of the cells that gets passed to the topological sorting algorithm that determines the reactive evaluation order. And that array is sorted last-edited-cell-first. So when we get these weird “mutable” interactions between cells, we get this. On a page refresh, it resets the cells to lexical order, resulting in the expected output (I’m guessing this is also why this went unnoticed in NK before: producing static output means it always works like a page refresh, right?)
Now, while this is the result of not using the idiomatic style of writing observable notebooks, bypassing the normal reactivity model, I do think it is a bug. In the “old style” passing mutable state between cells felt like you were really fighting the system, and that anything that broke was your own fault, but now it’s apparently much easier to do these things, and I expect people will do so because vanilla JavaScript is very mutable too.
Plus, it shouldn’t be that hard to ensure the cells are always in lexical order before being passed to the topological sorting algorithm for dependency tracking, right?
(PS: apologies for the wall of text, I’m too tired to be brief while braindumping)
Yep, that’s by design. Duplicate variable declarations don’t impede cell evaluation because variables declared by a cell shadow any top-level variables defined by other cells; however, duplicate variable declarations do break references from other cells because those references are ambiguous.
We’re working on jump to definition and the dataflow panel to make things easier to debug. I’m not sure what static export functionality you’re referring to (I don’t think we have anything planned there?), but duplicate top-level variable declarations won’t prevent imported code from running; they only prevent importing of duplicate symbols because again the references are ambiguous.
The order of evaluation is solely determined by topological references (dependencies, as you say). Anything else is nondeterministic and you should not depend on the order being reliable. If cells depend on each other, they should reference each other. You can declare dummy variables if needed to force these dependencies to be explicit, for example if a cell’s only function is to cause a side effect.
Yes, you can call display() multiple times from a cell and the output is appended, similar to console.log(). All displayed outputs are cleared when a cell is re-run. If you expose a cell’s display function to other cells, it has the same behavior as if the defining cell called it. I don’t know of any practical uses of exposing the display function to other cells, and it seems a bit messy to me, but I imagine someone will find creative uses for it. ![]()
Yes! It will be called the dataflow panel and in addition to showing the topology/dataflow of the notebook, it will allow you to inspect all top-level variables (rather than showing cell source code).
Thanks for the quick answers!
Ok, that if framed as shadowing variables locally in a cell it makes sense to me!
(It does make me miss how references to other cells were in bold before, because then its obvious when a variable is shadowed due to not being bold, making variable shadowing mistakes easy to spot and debug)
Sorry, that was a misunderstanding on my end! I think I saw somewhere that this is based on notebook kit, and confused that with going for the same actual features as notebook kit (which does say it supports static exports). Basically, I thought that being a web interface on top of it, it would also be compatible enough with it to support the same kind of exports like that.
Here’s the invalidation example from the system guide notebook refactored into a single, re-usable function to generate a button + canvas that reacts to the button click:
Summary
function makeColorBar(display, invalidation2){
// Do not use view! We cannot rely on reactivity between cells.
// However, Inputs still ensures that clicks.value is an integer
// value that increases by one each click.
const clicks = display(Inputs.button("Click", {label: "Run cell"}));
const colors = ["#4269d0", "#efb118", "#ff725c", "#6cc5b0"];
const duration = 2000;
const canvas1 = display(html`<canvas width="${width}" height="30" style="max-width: 100%; height: 30px;"></canvas>`);
const context1 = canvas1.getContext("2d");
let color = colors[0];
let start = performance.now();
function tick(now) {
const t = Math.min(1, (now - start) / duration);
context1.fillStyle = color;
context1.fillRect(0, 0, t * canvas1.width, canvas1.height);
if (t < 1) frame = requestAnimationFrame(tick);
}
let frame = requestAnimationFrame(tick);
// We need to manually set up our click event
// and do all the things that reactivity
// did for us earlier.
clicks.addEventListener("click", () => {
// this is needed to restart the animation
// in case it was finished.
Now, the original example that used three cells and reactivity was much more straightforward to write, and easier to follow. However if I’m writing a notebook where I want the same thing many many times but with small variations, this approach might be nicer (for example the makeTile function in my (as of yet unlisted) “Covering the grid with progressive multi-jittered sample sequences” notebook).
The other benefit of wrapping everything in one function in that kind of situation is that I don’t end up with global variable names like button1, button2, …, button20 and a less excessive number of cells.
We don’t have to pass display, but the alternative would be returning html`${clicks}${canvas}` at the end of the function. That’s fine here, but often there’s a lot more than just two elements in my prototypes, and then it’s harder to read or make adjustments to. Passing display makes iterative prototyping a lot easier!
TL;DR: I really like the power that display gives me! It feels like a safe way to “peek under the hood” compared to before


