prev 2021-07-20Movidea開発日記 image

Twitter

  • ソフトウェア開発でドッグフーティングが有用ならメンタリングでも有用な可能性があるのでは?と今年は採択者に課せられる発表資料作りを自分もやってみてるのだけど、そうすると「中間発表までにユーザテストした方がいいね」って言いながら「自分のはまだテストできる段階でない」という気持ちが…
  • つまりこれは「作り手がユーザテストに適切だと思うタイミングと横で見てる人が適切だと思うタイミングにはズレがある」ということ。作り手は「あとちょっとこれを実装したらもっと良くなる」が予測できるので、何が起きるか予測不可能なユーザテストをやることよりも目先の実装を優先してしまう(自戒
  • 今回僕はプロジェクト開始の発表の後に作り直しを決定したので「まだユーザテストできる状況ではない」と考えたくなってしまうが、これは典型的な「やらない理由探し」なんだよな。今すぐやることが適切でないにしても、なるべく早く検証するには何をやるべきか考えたりタスクをブレイクダウンすべき
  • ユーザテストをなぜやるかといえば「仮説を検証するため」
    • プロジェクトを進めてると未検証の仮説がたくさん生まれる(生まれたことに気づかないで「こうに違いない」と決めつけちゃうケースもあるが)
    • 仮説の検証にはコストが掛かるからなんでも検証することはできない
    • だから、「これを検証しないままプロジェクトを進めたら大きな手戻り損失が発生するかも知れないぞ」という確率と損失額の掛け合わせで検証の優先度が決まるのだと思う。
  • そう考えると前回Regroupでは「紙でやって僕が価値を感じた手法、電子化すればもっと価値が上がるに違いない」が仮説であり、ReactとTypeScriptを学びながら作ったプロトタイプだが、僕自身がリピーターになることで仮説が検証された。で、他の人にも価値があるかを検証することが必要
  • Regroupのままユーザテストをしようとすると「新規作成に直感的でない2操作が必要、さらにオススメの初期状態にするために正解のわかりにくい操作が必要」がまず悪い、想定デバイス「iPad+Apple Pencil」も厳しい、だから作り直した
  • だから、まず検証すべきことは「初めて使う人が戸惑わずに使い始められるか」なのだと思う。その後に「初めて使う人が価値を感じられるか」が続く。使い始められないツールで価値を感じることはあり得ないので使い始めるところが最優先。

新規作成リンクをクリックしたら新規作成ダイアログが表示されるべき

  • 付箋追加ダイアログと似てるけど別であるべき?
  • 複数行テキストをペーストしてみよう、のテキストを用意してあげる?
    • 単語
    • 短いフレーズ
    • これは短い文章です
    • これはとてもとても長い文章なので付箋にすると文字が小さくなります
  • 「適度な長さに刻むべき」は初めての人には負担が大きい
    • 一度先に進まないと「なぜそれが必要か」を理解できないから
  • 長すぎる行を刻むこと
    • 自動でやる?
    • デフォルトONのチェックボックスをつける?
  • チュートリアルバルーンだす?
    • あった方が良い?
    • バルーンで出すのではなく動画のヘルプをページごとに見れたらいいのでは
    • Oxygen Not Includedスタイル

複数付箋追加が行われた時、バラの付箋であるべきか、グループ化されているべきか

  • グループ化されてると便利
    • うっかり既存の付箋の上に追加してしまった時に直感的に修復できる
  • グループ化されてることのデメリット
    • 初めて使う人がいきなり「まずグループを選択して解除します」になる
    • いや、ならないか?
      • 解除しないでグループ化したまま動かせばいい
      • ドラッグしてグループの外に出したらいい
      • 「インポートしたけど使わなかったもの」をまとめて傍に退けておける →グループ化されててOK

「新規作成に直感的でない2操作が必要、さらにオススメの初期状態にするために正解のわかりにくい操作が必要」が悪い

  • なぜそんなことになったのか振り返り
  • URLルートへのアクセスはリンク置き場になった
    • image
    • Routerの設計の問題
    • 当初ヘルプ自体をRegroupでやろうとした
    • 保存されないサンドボックスなどへのリンクを置いた
    • 新規作成はクラウド保存されない

ルーティングがどうであるべきか

  • 初めての人が/に来るので、そこは余計な「選択肢から選ばせる」ことなくチュートリアルが開始するべき
    • Regroupは説明の乏しいリンクのリストがあるだけなのでよくない
    • Movideaはネット接続しないブランク画面
      • これはテストには便利なので残したいがこんな一等地にあるべきではない
  • ログイン不要で編集可能にしたいのでCapability URLで編集権限
    • viewとeditはURLでわかった方がよかった
    • Google Docsのメタファーだとviewとeditの他に「レイヤーを分けてコメントできる」があると面白そうだが、これは後ほどの実験とする

$ npx cypress open

:

You are currently running Version 7.7.0
To get the latest, run the following command*:
npm:npm install --save-dev cypress@8.0.0

Regroupのコードを見る

  • ルーティングをどうやるのだろう、でreact-routerを見様見真似で使ってるが、典型的なユースケースにマッチしないので結局hashの値を自分で処理してる
  • そもそも典型的なユースケースは複数ページある風の表現だが、僕のアプリはシングルページであり、別の画面が必要な時もページ遷移ではなくダイアログ表示なので典型的なユースケースに当てはまらなかった ts
import { Route, RouteComponentProps } from "react-router";
import { HashRouter } from "react-router-dom";
...
    <HashRouter>
      <Route path="/:id" component={WhenHashSpecified} />
 
      <Route exact path="/">
      ...
 
...
const WhenHashSpecified: React.FC<RouteComponentProps<{
  id: string;
}>> = props => {
  return <App urlParam={props.match.params.id} />;
};
 

というわけでルーティング部分を実装した

テストコードのcy.visit("/")cy.visit("/#blank")に変えたらテストがこけた こけた原因はcy.visit("/#blank")でアプリの状態がリセットされないこと

  • テストコードの途中で状態リセットのつもりでvisitしてたがリセットされなかった
  • そのため、直前での範囲選択を引きずったまま新しい画面を描画しようとしてエラー
  • さらにはそこで発生した問題が次のテストケースに影響した風に見える え?なんでリセットされないの?
  • cy.visit("/")ではリセットされてたよね?

cy.visit("/") の後の(A)では状態がリセットされているが、 cy.visit("/#blank")の後の(B)では値がリセットされていない ts

describe("visit_reset", () => {
  beforeEach(() => {
    cy.visit("/");
  });
 
  it("main", () => {
    cy.updateGlobal((g) => {
      g.scale = 2;
    })
    cy.getGlobal(g => g.scale).should("eql", 2);
 
    cy.visit("/");
    cy.getGlobal(g => g.scale).should("eql", 1);  // (A)
 
    cy.updateGlobal((g) => {
      g.scale = 2;
    })
 
    cy.visit("/#blank");
    cy.getGlobal(g => g.scale).should("eql", 2);  // (B)
  });
});

reported https://github.com/cypress-io/cypress/issues/17479

テスト用のページに新しいURLを割り当てて、ルートにアクセスしたら付箋追加ダイアログが開いた状態で開始するようになった

次は複数付箋の追加をつける

  • ダイアログはつけてある
  • グループ化する

その次

  • 付箋単体のドラッグでの移動(今はグループだけ)
  • グループの中から外へ
    • ドラッグターゲットがグループであるかどうかをハイライトで示したい

2021-07-20Movidea開発日記の図に加筆 image うーむ、記法をなんとかしないと

  • イベントハンドラ
  • 行いたい処理
  • 要素の包含関係によりstopPropagationで止める関係
  • 時系列の動的スコープ image

stopPropagation、そんな目立つ必要ないな、あと動的スコープはもっとはっきりしてた方がいいし、色が同じであることで同じものと認識するのは良くないな、名前があるべき

いま「選択状態か」が「選択されたものが存在するか」で判断してるけど、選択範囲に対象が存在しなかった時に選択範囲表示を消すか、選択対象がなくても選択状態に入るようにすべきか

  • 後者の方が自然かな

閉じたグループの表札付箋を普通の付箋と同様にレンダリングしようとしてコードを再利用したのが良くなかった

  • 再利用できるようにidがItemIdではなくstringになるようにしたため、ドラッグ対象に指定しようとして型エラー
  • 再利用の単位が間違っていた
  • コンポーネントはFusenとNameplateFusenに分離した
  • 共通して必要なフォントサイズ調節の処理はカスタムフックにする

付箋のdragstartが親のグループに伝搬しないようにする必要がある

  • 図中の赤線
    • image

できた image

  • これでRegroup相当の移動はできたかな
  • 図の更新
    • image
  • いまはグループがdropを受け取らないので貫通してキャンバスにdropが起きてる
    • これをグループにdropしたら今の処理、キャンバスにdropしたらグループから出す、ということができれば良い

パターン発見した

  • image
  • 共通のイベントハンドラで異なる処理をしたい
    • 片方が「処理をしない」なのも含まれる
  • この時「どちらの処理をするのか」の情報がイベントハンドラの外から得られる必要があり、それはつまり動的スコープである

キャンバスにドロップしたのかグループにドロップしたのかによって挙動が変わるなら今どちらであるのかをユーザがわかる必要がある image この実装で、図解にない処理が必要になった image

  • ドロップターゲットの中に別の要素がある時に、dragleaveイベントが発生してしまう
  • dragenterが先に起こるので参照カウント的な方法で外に出たかどうかを判断する

ts

  let enter_count = 0;
  const onDragEnter = (e: React.DragEvent<HTMLDivElement>) => {
    enter_count++;
    if (self.current !== null) {
      self.current.style.borderColor = GROUP_HIGHLIGHTED_BORDER_COLOR;
    }
    e.stopPropagation();
  }
  const onDragLeave = (e: React.DragEvent<HTMLDivElement>) => {
    enter_count--;
    if (self.current !== null && enter_count === 0) {
      self.current.style.borderColor = GROUP_BORDER_COLOR;
    }
    e.stopPropagation();
  }
  const onDrop = (e: React.DragEvent<HTMLDivElement>) => {
    if (self.current !== null) {
      self.current.style.borderColor = GROUP_BORDER_COLOR;
    }
    onGroupDrop(e, value);
  }

2021-07-27Movidea開発日記