static get properties() {
    return {
      // this.clipMode = "hidden"
      extent: {defaultValue: pt(100,20)},

      html: {
        derived: true,
        initialize() {},
        get() { return ""; },
        set(x) {}
      },

      haloShadow: {
        readOnly: true,
        get(){
           return {
             blur: 10,
             color: Color.rgb(52,152,219),
             distance: 0,
             rotation: 45
           } 
         }
      },

      domNodeTagName: {readOnly: true, get() { return "input"; }},
      domNodeStyle: {readOnly: true, get() { return "background: grey"; }},

      input: {
        derived: true, after: ["domNode"],
        get() { return (this.domNode && this.domNode.value) || ""; },
        set(val) { this.domNode.value = val; this.updateHtml(this.input); }
      },

      placeholder: {
        after: ["domNode"],
        set(val) { this.setProperty("placeholder", val); this.updateHtml(this.input); }
      },

      fontSize: {
        defaultValue: 12, after: ["input"],
        set(value) { this.setProperty("fontSize", value); this.updateHtml(this.input); }
      },

      fontFamily: {
        defaultValue: "sans-serif", after: ["input"],
        set(value) { this.setProperty("fontFamily", value); this.updateHtml(this.input); }
      },

      padding: {
        defaultValue: Rectangle.inset(2), after: ["input"],
        set(value) { this.setProperty("padding", value); this.updateHtml(this.input); }
      }
    }
  }
export async function interactivelySaveWorld(world, options) {
  options = {showSaveDialog: true, useExpectedCommit: true, errorOnMissingExpectedCommit: false, confirmOverwrite: true, ...options};

  let name = world.name, tags = [], description = "",
      oldCommit = await ensureCommitInfo(Path("metadata.commit").get(world)),
      db = options.morphicdb || MorphicDB.default;

  if (options.showSaveDialog) {
    let dialog = await loadObjectFromPartsbinFolder("save world dialog"),
        {commit, db: dialogDB} = await world.openPrompt(dialog, {targetWorld: world});
    if (dialogDB) db = dialogDB;
    ({name, tags, description} = commit || {});
    if (!name) return null;
  } else if (oldCommit) {
    ({name, tags, description} = oldCommit);
  }

  let i = LoadingIndicator.open(`saving ${name}...`);
  await promise.delay(80);

  try {
    let snapshotOptions = {previewWidth: 200, previewHeight: 200, previewType: "png"},
        ref = "HEAD",
        oldName = oldCommit ? oldCommit.name : world.name,
        expectedParentCommit;

    if (options.useExpectedCommit) {
      if (oldName !== name && options.confirmOverwrite) {
        let {exists, commitId: existingCommitId} = await db.exists("world", name);
        if (exists) {
          let overwrite = await world.confirm(`A world "${name}" already exists, overwrite?`, {styleClasses: ['Halo'], fill: Color.rgba(0,0,0,0.8)});
          if (!overwrite) return null;
          expectedParentCommit = existingCommitId;
        }
        world.name = name;
      } else {
        expectedParentCommit = oldCommit ? oldCommit._id : undefined;
      }
    }

    let commitSpec = {
          author: world.getCurrentUser(),
          message: "world save",
          tags, description
        },
        commit = await db.snapshotAndCommit(
          "world", name, world, snapshotOptions,
          commitSpec, ref, expectedParentCommit);

    // hist
    if (window.history) {
      let queryString = typeof document !== "undefined" ? document.location.search : "",
          path = pathForBrowserHistory(name, queryString);
      window.history.pushState({}, "lively.next", path);
    }

    world.setStatusMessage(`saved world ${name}`);
    world.get("world-list") && world.get("world-list").onWorldSaved(name);

    return commit;

  } catch (err) {

    if (err.message.includes("but no version entry exists") && !options.errorOnMissingExpectedCommit) {
      return interactivelySaveWorld(world, {...options, morphicdb: db, useExpectedCommit: false, showSaveDialog: false});
    }

    let [_, typeAndName, expectedVersion, actualVersion] = err.message.match(/Trying to store "([^\"]+)" on top of expected version ([^\s]+) but ref HEAD is of version ([^\s\!]+)/) || [];
    if (expectedVersion && actualVersion) {
      let [newerCommit] = await db.log(actualVersion, 1, /*includeCommits = */true);
      let overwrite = true;
      if (options.confirmOverwrite) {
        let {author: {name: authorName}, timestamp} = newerCommit,
            overwriteQ = `The current version of world ${name} is not the most recent!\n`
                       + `A newer version by ${authorName} was saved on `
                       + `${date.format(new Date(timestamp), "yyyy-mm-dd HH:MM")}. Overwrite?`;
        overwrite = await world.confirm(overwriteQ);        
      }
      if (!overwrite) return null;
      world.changeMetaData("commit", obj.dissoc(newerCommit, ["preview"]), /*serialize = */true, /*merge = */false);
      return interactivelySaveWorld(world, {...options, morphicdb: db, showSaveDialog: false});
    }

    console.error(err);
    world.logError("Error saving world: " + err);
  } finally { i.remove(); }

}