I find myself opening and using the web console to do simple tasks like string manipulation, maths, sorting often. I wanted a dedicated and persistent way to do this. And also just a place to keep notes, urls and stuff.
So I made this "literate programming" notepad.
I'm also former Emacs user, and I like the lisp scratch buffer there, which is similar.
I'm also building a web-based literate programming environment called Starboard[1] that's probably a hundred times the amount of code (but then it has additional features such as top-level await, plugin, and Python support).
Consider supporting lit-html[2] literals instead of strings for HTML output, it works really well in notebook environments.
One more thing you can consider: a esm tagged literal that uses import(<data url of the code>), you can then use ES Module imports to dynamically load code. Here's a blog post about that approach [3]
The source is very small and can be used both through the file:// and hosted http protocols. So you can easily make it do whatever you like.
I could maybe expose some global variables to make it customizable, but I went for simplicity and smaller source instead.
Ctrl-Enter is the shortcut for inserting a soft newline for content-editables on Safari btw. You might want to consider supporting command-enter for this reason alone.
Do you, or the broader community, have any ideas about solving infinite loops? I'm on mobile so I can't test this at the moment, but I imagine that while(1) crashes the tab.
What would an MVP operating system like ctrl-C functionality look like for execution environments in the browser?
I am aware of multiple hacky solutions, such as loop detection and adding timeouts. This fails in most non-trivial creative coding applications due to long running code. I'm interested what it would take to come up with a general escape hatch like any shell user has.
Wow, that seems hard to do. One would need to take a lot of things in account, including recursive calls, asynchronous functions / calls and, indeed, even long strings of instructions that are not necessarily part of a loop or recursive calls.
Would a transform that adds the check between every JS instructions where it is possible theoretically solve the problem? is there a solution that does not slow down the code too much and interrupts the code within an acceptable margin?
Yeah! The general case of this is the halting problem...
The best solution I know of is stopify, which the other comments have talked about. I just wonder if there's another take on the situation, something akin to OS task management.
On Starboard[0] I approached this by sandboxing the notebook code in an iframe on a different origin. This sandboxing has to be done anyway to prevent XSS.
If you type while(true){} in a notebook only the iframe will break (and usually your browser will prompt you after a while to kill it). When you do only the iframe is no longer functional.
I don't think there's an elegant way to solve it any differently in the browser.
That's clever. Do you know if there is something possible using web workers? maybe running the "sandboxed code" in the worker? I don't really know how they work and if it is possible to interrupt them from the main thread.
I think so, but a worker won't have access to the DOM and a bunch of other APIs, so the code would be fairly limited in what it can do. Which may be fine for some usecases!
Hi, yes! I like your project. I chatted about similar things on your launch post here. You also address this explicitly on your product which I appreciate.
What I'm getting at is that these browser notebooks try to get at the desire and feeling for rapid exploration and iteration. Losing context by having a crashing logic error is a massive blow to that ideal.
I'm not saying that your or anyone elses product is only for "rapid prototyping" but it's still true that larger projects could be bit by the same errors. When I crash my native code I C-c and I'm back in an instant. When I crash browser notebook code I lose a bit of time and unsaved code. I crash browsers often in creative coding where I write many loops and don't always do them right.
It may also be that my chrome and firefox experience on linus is worse than standard, I don't know. But I have crashed my entire browser in chrome when using observable, and I thought that wasn't supposed to be possible.
You could modify the AST tree using something like jscodeshift to add a function that is called in every loop (and maybe every function) and there you can "break" the loop in a pretty clean way.
I have made something similar (https://easylang.online/ide/). It is a language of its own, which is compiled and interpreted by WASM. The problem with hanging in endless loops is solved by running the interpreter in a "web worker" that can be killed and restarted at any time.
We started with a blacklist to match against while(1), while(true), for(;;), etc, but we eventually found an eslint plugin (goedel.js) that nicely tells you if the code contains an infinite loop or recursion.
To add to this comment, Stopify is a JS-to-JS compiler that instruments sync JS code to make them interruptible at set points. The paper [0] can explain it better than I ever could.
I work on an experimental Pyret [1] runtime that uses Stopify to instrument compiled Pyret code (plain old JS) so that we can run Pyret code on the main page thread without hanging it up. Main thread execution is important for quick/easy DOM access. In terms of performance cost, we haven't measured too extensively, but so far, on average, we're seeing a 2x slow down compared to un-Stopified programs.
Do you see any other solutions in the same domain as Stopify? Another method that might provide a way to keep UI unblocked but still have user executable code?
If you need the user code to execute on the main thread, then unfortunately I am aware of none besides bundling your own tailored system.
Pyret used to use its own runtime system [0] but Stopify was created in part to replace it due to the maintenance burden and complexity of "vanilla" JS interoperability.
Codepen uses a system that measures loop duration, and it's a giant pain. Having done some pens that do ray tracing and image transforms which can have long running loops. Given the variable execution time of JS it can be quite random. It just exits the loop without warning, causing weird failures in your code.
2 theoretical solutions ( with significant overhead ) are:
Run the code in a VM ( maybe quick.js compiled to WASM would work ) that suspends code execution periodically if it exceeds a certain duration. This has the advantage that long running code in general won't block rendering, not just loops.
Transform the AST to use async generator that yields once per loop. This would allow the loop to be suspended and resumed. But it would require a lot of modification to the AST, making effectively the entire call tree async.
I've done this and it works surprisingly well. I made a timesliced js scripting system this way.. it looked imperative with tight loops but it was all asynchronous. It felt like a threaded app.
Well you can easily detect trivial examples like "while(1)" and "for(i=0;true;i++)". But otherwise how would know some is an infinite loop?
Put a bit more simply, to work out if a problem is unsolvable (infinitly looping) you need to evaluate the problem... By trying to solve it. Checkout the halting problem for more details.
"Solving" infinite loops doesn't necessarily mean accurately predicting a priori whether a piece of code will terminate. It can just mean ensuring that if the code does try to run indefinitely, it doesn't have unfortunate effects such as blocking the UI thread without the possibility of being interrupted.
> It can just mean ensuring that if the code does try to run indefinitely, it doesn't have unfortunate effects such as blocking the UI thread without the possibility of being interrupted.
Well that can be achieved by executing the code in a background worker thread. Which doesn't affect the UI thread in browsers... no sure how it's managed but I think you could terminate it after a certain amount of time too
Recommendation: when inserting the results of the evaluation, add it to the selection so "reverting" the inserted text is as easy as hitting Backspace or Delete.
I can no longer get an empty line at the bottom of the document. My cursor can only ever be within the outputted "<style>" tags, and hitting return from there creates a new pair of "<style>" tags.
To temper this bug-report with a cool feature, I like that the output is (usually, with the exception of the above) plain text, which can itself be executed. I haven't found a use-case for this, but I'm sure there's some interesting meta-programming that could be done.
(Now fixed) Thanks for letting me know, there is indeed a small issue with the clear() function.
I considered supporting only plain-text, but I found the editable visible style tag very interesting.
And I like the fact all the power of the browser is available, e.g canvas, 3d rendering, and simple stuff like headers and images.
Of course, you can use only plaintext if you'd like.
If the result is an Element (or perhaps Node) insert it into the document instead of for example "[object HTMLCanvasElement]"
A potentially more difficult one would be automagically hoisting functions defined in the document when you hit ctrl-enter.
function factorial(n) { ...the usual... }
factorial(7);// [ctrl-enter] on just this line works
Felt a bit odd that "running" a line didn't drop the cursor down past the output, ready for the next line; but the current seems useful for editing and re-trying a line, also interesting!
68 comments
[ 1.8 ms ] story [ 121 ms ] threadI'm also former Emacs user, and I like the lisp scratch buffer there, which is similar.
I'm also building a web-based literate programming environment called Starboard[1] that's probably a hundred times the amount of code (but then it has additional features such as top-level await, plugin, and Python support).
Consider supporting lit-html[2] literals instead of strings for HTML output, it works really well in notebook environments.
One more thing you can consider: a esm tagged literal that uses import(<data url of the code>), you can then use ES Module imports to dynamically load code. Here's a blog post about that approach [3]
[1]: https://starboard.gg [2]: https://lit-html.polymer-project.org/ [3]: https://2ality.com/2019/10/eval-via-import.html
My issue with these notebooks is they always get very cluttered with inline outputs after a couple 100 lines.
https://chrome.google.com/webstore/detail/markdown-new-tab/d...
Would be neat to see this integrated in somehow.
`document.addEventListener('keydown', (e) => (e.metaKey && e.code === 'Enter' && evaluate() && e.preventDefault()));`
The metaKey[1] is only accessible for keydown.
[1] https://stackoverflow.com/questions/3902635/how-does-one-cap...
What would an MVP operating system like ctrl-C functionality look like for execution environments in the browser?
Browsers will complain against code running for too long without interruption though.
Wow, that seems hard to do. One would need to take a lot of things in account, including recursive calls, asynchronous functions / calls and, indeed, even long strings of instructions that are not necessarily part of a loop or recursive calls.
Would a transform that adds the check between every JS instructions where it is possible theoretically solve the problem? is there a solution that does not slow down the code too much and interrupts the code within an acceptable margin?
If you type while(true){} in a notebook only the iframe will break (and usually your browser will prompt you after a while to kill it). When you do only the iframe is no longer functional.
I don't think there's an elegant way to solve it any differently in the browser.
[0]: https://starboard.gg
What I'm getting at is that these browser notebooks try to get at the desire and feeling for rapid exploration and iteration. Losing context by having a crashing logic error is a massive blow to that ideal.
I'm not saying that your or anyone elses product is only for "rapid prototyping" but it's still true that larger projects could be bit by the same errors. When I crash my native code I C-c and I'm back in an instant. When I crash browser notebook code I lose a bit of time and unsaved code. I crash browsers often in creative coding where I write many loops and don't always do them right.
It may also be that my chrome and firefox experience on linus is worse than standard, I don't know. But I have crashed my entire browser in chrome when using observable, and I thought that wasn't supposed to be possible.
I work on an experimental Pyret [1] runtime that uses Stopify to instrument compiled Pyret code (plain old JS) so that we can run Pyret code on the main page thread without hanging it up. Main thread execution is important for quick/easy DOM access. In terms of performance cost, we haven't measured too extensively, but so far, on average, we're seeing a 2x slow down compared to un-Stopified programs.
(Disclaimer: paid contributor for Pyret).
[0] https://www.stopify.org/research.html
[1] https://www.pyret.org/
Pyret used to use its own runtime system [0] but Stopify was created in part to replace it due to the maintenance burden and complexity of "vanilla" JS interoperability.
[0] https://www.pyret.org/docs/latest/s_running.html
2 theoretical solutions ( with significant overhead ) are:
Run the code in a VM ( maybe quick.js compiled to WASM would work ) that suspends code execution periodically if it exceeds a certain duration. This has the advantage that long running code in general won't block rendering, not just loops.
Transform the AST to use async generator that yields once per loop. This would allow the loop to be suspended and resumed. But it would require a lot of modification to the AST, making effectively the entire call tree async.
Put a bit more simply, to work out if a problem is unsolvable (infinitly looping) you need to evaluate the problem... By trying to solve it. Checkout the halting problem for more details.
https://en.m.wikipedia.org/wiki/Halting_problem
Well that can be achieved by executing the code in a background worker thread. Which doesn't affect the UI thread in browsers... no sure how it's managed but I think you could terminate it after a certain amount of time too
OS implementations dont solve the halting problem. I agree with the sibling comment.
1) What is your monetization strategy?
2) Will this be supported forever?
...
... /s
:) Nice project, useful.
EDIT: this was obviously satirical...
Small issue: I find that the cursor can be hard to use sometimes. For example, if I clear and then execute the single line
I can no longer get an empty line at the bottom of the document. My cursor can only ever be within the outputted "<style>" tags, and hitting return from there creates a new pair of "<style>" tags.To temper this bug-report with a cool feature, I like that the output is (usually, with the exception of the above) plain text, which can itself be executed. I haven't found a use-case for this, but I'm sure there's some interesting meta-programming that could be done.
I considered supporting only plain-text, but I found the editable visible style tag very interesting. And I like the fact all the power of the browser is available, e.g canvas, 3d rendering, and simple stuff like headers and images. Of course, you can use only plaintext if you'd like.
factorial(7);// [ctrl-enter] on just this line works
Felt a bit odd that "running" a line didn't drop the cursor down past the output, ready for the next line; but the current seems useful for editing and re-trying a line, also interesting!