Notes after 3 days of heavy use

I’ve been using Bike instead of TaskPaper as my ‘home for text/todos’ for the past 3 days. Some notes I’ve jotted down (at the bottom of my main Bike document):

  • Like: cursor animation when clicking to new insertion point
  • Like: fold/unfold animation
  • No clickable URLs? :slightly_frowning_face:
  • Tapping vs. moving triangle is sometimes glitchy. Ex: I’ll intend to fold a section and it instead moves into the section above. I imagine this can be changed by having a threshold move distance, under which the ‘move’ is treated as a tap?
  • Annoying: copying+pasting indented content and having to remove spaces from the start of each string (Happened when I copy+pasted this list)
  • I will try to move a branch and it ends up in some weird place I didn’t intend
  • I much prefer Bike’s ‘delete’ behavior (return to end of prev line) instead of un-indenting
  • Some of the limitations compared to TaskPaper are making me more disciplined: no weird spacing between projects, using dashed lines to create new sections
  • [Note: I disabled most animations on day 2 and haven’t wanted to turn it back on yet. Although I’m not exactly sure what I turned off?]

Overall: very pleased! Bike is more polished than I expected.

1 Like

Thanks for the detailed report, I’ll try to get these issues addressed.

  • Like: cursor animation when clicking to new insertion point
  • Like: fold/unfold animation

Note: I disabled most animations on day 2 and haven’t wanted to turn it back on yet. Although I’m not exactly sure what I turned off?

If “Disable Most Animations” is checked then I think those first two items shouldn’t be animating anymore? Generally when animations are disabled the only ones that I keep are scrolling related… page up, page down, etc.

No clickable URLs?

This and in general text decorations is probably the biggest needed 1.0 feature that’s still missing in my mind. Won’t be ready in next release, but I’m hopeful that I’ll have something going towards the end of next week.

1 Like

Oh, I think I must have thought I turned them off, but didn’t actually turn them off. That explains why I didn’t notice! I think I’ll keep animations on then…

1 Like

Can you expand on this a bit. It this related to the tapping vs. moving triangle problem or something else? Are you talking about moving with drag and drop, or keyboard commands?

I was having the jumpiness issue with one of the nodes, so I got a screen recording of the sort of thing I’m talking about.The key thing: I was only ever trying to fold/unfold, so all the other behavior is unintended.

[I tried converting to a gif for sharing. I can send the video if you want.]

animation

I experience the trying-to-move-but-goes-to-unintended-place problem when moving with drag and drop.

In the meanwhile – as a stop gap – I’ve been using a Keyboard Maestro macro (copies text of selected node, extracts first link, adds a document UUID if it’s a bike:// link and doesn’t have one)

Rough, and temporary, but others are welcome to experiment if they happen to use Keyboard Maestro:

Follow first link in Bike item.kmmacros.zip (3.9 KB)

Expand disclosure triangle to view JS Source
(() => {
    "use strict";

    ObjC.import("AppKit");

    // Rough interim script for:
    // extracting first link from the clipboard
    // supplying a document id if its a bike:// link
    // without one, and opening the link through the shell

    // Draft 0.00  Rob Trew @2022

    // main :: IO ()
    const main = () =>
        either(
            alert("Open link from Bike")
        )(
            url => (
                Object.assign(
                    Application.currentApplication(), {
                        includeStandardAdditions: true
                    }
                ).doShellScript(`open ${url}`),
                url
            )
        )(
            bindLR(
                clipTextLR()
            )(txt => bindLR(
                firstLinkFoundLR(txt)
            )(url => url.startsWith(
                "bike://#"
            ) ? (
                bindLR(
                    filePathFromFrontWindowLR()
                )(fp => bindLR(
                    readFileLR(fp)
                )(xml => bindLR(
                    xmlDocFromStringLR(xml)
                )(doc => bindLR(
                    bikeDocIdFromXmlDocLR(doc)
                )(id => Right(
                    `bike://${id}${url.slice(7)}`
                )))))
            ) : Right(url)))
        );

    // --------------- FIRST LINK IN TEXT ----------------

    // firstLinkFoundLR :: String -> Either String URI
    const firstLinkFoundLR = s => {
        // Either a message or the first URI
        // found in the given string.
        const parts = s.split("://");

        return 1 < parts.length ? (() => {
            const [as, bs] = parts.map(words);
            const
                a = 0 < as.length ? (
                    last(as)
                ) : "",
                b = 0 < bs.length ? (
                    bs[0]
                ) : "";

            const
                scheme = last(a.split(/\b/u)),
                resource = takeWhile(
                    c => "!#$&'*+,/:;=?@[]".includes(s) || (
                        !(/[\s()]/u).test(c)
                    )
                )([...b]).join("");

            return Boolean(scheme) ? (
                Boolean(resource) ? (
                    Right(`${scheme}://${resource}`)
                ) : Left(`No resource found: ${scheme}://`)
            ) : Left(`No scheme found: ://${resource}`);
        })() : Left("No link found in string.");
    };

    // ----------------------- JXA -----------------------

    // alert :: String => String -> IO String
    const alert = title =>
        s => {
            const sa = Object.assign(
                Application("System Events"), {
                    includeStandardAdditions: true
                });

            return (
                sa.activate(),
                sa.displayDialog(s, {
                    withTitle: title,
                    buttons: ["OK"],
                    defaultButton: "OK"
                }),
                s
            );
        };


    // bikeDocIDFromXmlDoc :: NSXMLDoc ->
    // Either String String
    const bikeDocIdFromXmlDocLR = doc => {
        // Either a message or the ID string of the root
        // <ul> tag of a Bike document parsed as XML.
        const
            uw = ObjC.unwrap,
            e = $(),
            rootUL = uw(
                doc.nodesForXPathError("//body/ul", e)
            )[0];

        return rootUL.isNil() ? (
            Left(uw(e.localizedDescription))
        ) : Right(
            uw(rootUL.attributeForName("id").stringValue)
        );
    };


    // filePathFromFrontWindowLR  :: () -> Either String FilePath
    const filePathFromFrontWindowLR = () => {
        // ObjC.import ('AppKit')
        const
            appName = ObjC.unwrap(
                $.NSWorkspace.sharedWorkspace
                .frontmostApplication.localizedName
            ),
            ws = Application("System Events")
            .applicationProcesses.byName(appName).windows;

        return bindLR(
            0 < ws.length ? Right(
                ws.at(0).attributes.byName("AXDocument")
                .value()
            ) : Left(
                `No document windows open in ${appName}.`
            )
        )(
            docURL => null !== docURL ? (
                Right(decodeURIComponent(docURL.slice(7)))
            ) : Left(
                `No saved document active in ${appName}.`
            )
        );
    };


    // clipTextLR :: () -> Either String String
    const clipTextLR = () => {
        // Either a message, (if no clip text is found),
        // or the string contents of the clipboard.
        const
            v = ObjC.unwrap(
                $.NSPasteboard.generalPasteboard
                .stringForType($.NSPasteboardTypeString)
            );

        return Boolean(v) && 0 < v.length ? (
            Right(v)
        ) : Left("No utf8-plain-text found in clipboard.");
    };


    // xmlDocFromStringLR ::
    // XML String -> Either String NSXMLDocument
    const xmlDocFromStringLR = xml => {
        const
            error = $(),
            node = $.NSXMLDocument.alloc
            .initWithXMLStringOptionsError(
                xml, 0, error
            );

        return node.isNil() ? (
            Left("File could not be parsed as XML")
        ) : Right(node);
    };


    // --------------------- GENERIC ---------------------

    // Left :: a -> Either a b
    const Left = x => ({
        type: "Either",
        Left: x
    });


    // Right :: b -> Either a b
    const Right = x => ({
        type: "Either",
        Right: x
    });


    // bindLR (>>=) :: Either a ->
    // (a -> Either b) -> Either b
    const bindLR = m =>
        mf => m.Left ? (
            m
        ) : mf(m.Right);


    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = fl =>
        // Application of the function fl to the
        // contents of any Left value in e, or
        // the application of fr to its Right value.
        fr => e => e.Left ? (
            fl(e.Left)
        ) : fr(e.Right);


    // last :: [a] -> a
    const last = xs =>
        // The last item of a list.
        0 < xs.length ? (
            xs.slice(-1)[0]
        ) : null;


    // readFileLR :: FilePath -> Either String IO String
    const readFileLR = fp => {
        // Either a message or the contents of any
        // text file at the given filepath.
        const
            e = $(),
            ns = $.NSString
            .stringWithContentsOfFileEncodingError(
                $(fp).stringByStandardizingPath,
                $.NSUTF8StringEncoding,
                e
            );

        return ns.isNil() ? (
            Left(ObjC.unwrap(e.localizedDescription))
        ) : Right(ObjC.unwrap(ns));
    };


    // takeWhile :: (a -> Bool) -> [a] -> [a]
    const takeWhile = p =>
        xs => {
            const i = xs.findIndex(x => !p(x));

            return -1 !== i ? (
                xs.slice(0, i)
            ) : xs;
        };


    // words :: String -> [String]
    const words = s =>
        // List of space-delimited sub-strings.
        s.split(/\s+/u);

    // ---
    return main();
})();

FWIW I notice that on this system (macOS 11.6.3, Bike Preview 26):

  1. The bike://UUID#itemID links are working well (Copy > Copy Link)
  2. but from browsers and applications supporting live links, I get a message if I try to follow one of the (Copy > Copy Path Link) variants which include a file path:

(Failed to find any documents matching the link)

(Perhaps that part of the bike url scheme is not yet implemented in build 26)

3 Likes

Thanks. Hard to tell from video, but I “think” it might be just due to the original problem of drags starting when you don’t really mean them to. I’ll get that fixed soon. I haven’t noticed such problems myself, but I’m using a trackpad, maybe that’s less wiggly than a mouse.

Another possibility is that it just got into some weird state. I don’t use drag and drop much myself, so that’s probably not all that well tested. Anyway I’ll first try to fix the drag starting to soon issue, if that doesn’t solve problem that I’ll debug further.

2 Likes

Thank you for looking into that! (FTR I use a trackpad)

Working through this list :slight_smile: … didn’t quite understand this note. I know still missing many features compared to TaskPaper. I just am not sure what exactly you mean here.

1 Like

I’ve tried a couple times to explain what I was thinking here but I’m not sure I can clearly articulate it. Part of it is that: I find it easier to build out a structure when I’m not having to decide when something is a project vs. todo vs. note.

2 Likes

I agree – initial flow and structuring seems freest with minimal differentiation.

(In practice I barely make any use of project ⇄ task ⇄ note distinctions in TaskPaper these days – almost everything is an unadorned indented line

(a note, for the parser)

1 Like

Interesting to hear, thanks for letting me know.

I agree there sure is something nice about limited visuals and features. It’s tricky, because at the same time there sure is something nice about adding lots of nice new features! :)… I certainly do plan to add a few more major sets of features to Bike. At the same time I hope to keep the “feel” similar to what it is now.

I think this idea ties into the payment discussion too. On one hand apps with limited features are nice. On the other hand if you want to charge for upgrades apps need to keep getting new “visible” features… maybe it’s also true that to keep membership subscribers apps also need to keep getting new features (but I think a little less the case if the subscription price is low enough)

1 Like